Идиоматический способ написать концепцию, которая говорит, что тип является std :: vector - PullRequest
0 голосов
/ 26 июня 2018

У меня есть следующий код, который реализует следующие черты типа:

  • этот тип std::vector
  • этот тип std::vector из целых чисел

Это работает, но довольно многословно.
Есть ли более короткий / более хороший способ написать это, используя концепции?
Я знаю, что могу украсть концепции из range-v3 или какой-либо другой подобной библиотеки, но давайте предположим, что я хочуреализовать это сам.

#include <iostream>
#include <string>
#include <type_traits>
#include <vector>

template <class T>
struct is_vector {
  static constexpr bool value = false;
};
template <class T, class A>
struct is_vector<std::vector<T, A> > {
  static constexpr bool value = true;
};

template <class T>
struct is_vector_of_int {
  static constexpr bool value = false;
};

template <class A>
struct is_vector_of_int<std::vector<int, A> > {
  static constexpr bool value = true;
};

// TODO add _v bool

template<typename T>
concept bool Vec = is_vector<T>::value;

template<typename T>
concept bool VecInt = is_vector_of_int<T>::value;

struct my_allocator : public std::allocator<int>{
};

template<VecInt V>
size_t func (const V& v){
    return v.size();
}
int main()
{
    static_assert(!is_vector<std::string>::value);
    static_assert(is_vector<std::vector<int, my_allocator>>::value);
    static_assert(is_vector<std::vector<int, std::allocator<int>>>::value);

    static_assert(!is_vector_of_int<std::string>::value);
    static_assert(is_vector_of_int<std::vector<int, my_allocator>>::value);
    static_assert(!is_vector_of_int<std::vector<float, my_allocator>>::value);

    static_assert(Vec<std::vector<float, my_allocator>>);
    static_assert(!VecInt<std::vector<float, my_allocator>>);
    static_assert(Vec<std::vector<int>>);
    std::vector<float> vf{1.1,2.2,3.3};
    std::vector<int> vi{1,2,3};
    // std::cout << func (vf);
    std::cout << func (vi);
}

Ответы [ 4 ]

0 голосов
/ 26 июня 2018

Я не знаю лучшего способа написать вашу концепцию Vec, чем со специализацией is_vector.Но VecInt можно упростить до:

template <typename T>
concept bool VecInt =
    Vec<T> && std::is_same_v<typename T::value_type, int>;

(Кроме того, доступная экспериментальная поддержка концепций g ++ основана на более старом предложении, чем предложение, принятое в C ++ 20. Таким образом, хотя текущий g ++-fconcepts требует concept bool VecInt = ..., C ++ 20 вместо этого потребует concept VecInt = ..., отбрасывая часть bool. Конечно, тип специализации концепта всегда bool, поэтому он считался ненужнымтам.)

Это также вызывает еще одно улучшение.Предположим, что вместо одного шаблона функции func у вас было две перегрузки:

template <VecInt V> std::size_t func(const V&);  // #1
template <Vec V> std::size_t func(const V&);     // #2

Если вы попытаетесь передать std::vector<double> в func, ограничение шаблона # 1 не будет выполнено,поэтому будет использоваться шаблон № 2.Но если вы попытаетесь передать std::vector<int> на func, что произойдет?Ответ в том, что при использовании VecInt вызов неоднозначен, но при использовании моего VecInt используется шаблон № 1.

В старых шаблонах неограниченных функций C ++ определяет «более специализированный, чем«связь, которая может определять многие случаи того, что« шаблон функции X может быть вызван с определенным списком типов аргументов, логически подразумевает, что шаблон функции Y может быть вызван с теми же аргументами ».Более старая логика для этого отношения основана на типах параметров функции.Например, g(std::list<int>&) более специализирован, чем g(std::list<T>&) более специализирован, чем g(T&) более специализирован, чем g(const T&).Это помогает C ++ иногда естественным образом «делать то, что я имею в виду», когда существует несколько шаблонов функций с одинаковыми именами.

Как показано в этом примере, иногда удовлетворение одной концепции или ограничения логически подразумевает выполнение другой концепции или ограничения, и этобыло бы хорошо, если бы это означало, что C ++ может использовать этот факт для определения «более специализированного, чем» для перегрузок шаблона функции (и частичной специализации шаблона класса).Но шаблонные ограничения - более сложная вещь, чем типы параметров, и определить их логическое значение, как правило, гораздо сложнее.

Таким образом, C ++ определяет только некоторые довольно простые правила для сравнения ограничений в случаях, когда все ограничения обоих шаблонов удовлетворяются.,Не вдаваясь в точные технические детали, нужно помнить следующие основные моменты:

  1. При сравнении ограничений любое выражение, НЕ являющееся одной из этих четырех вещей, обрабатывается как неизвестное логическое значение:

    а.Специализация понятия.

    b.Выражение вида E1 && E2.

    c.Выражение вида E1 || E2.

    d.Выражение, которое представляет собой число наборов ( круглых скобок ) вокруг любого из вышеперечисленных.

  2. При сравнении ограничений два выражения, которые не входят в вышеуказанные категории, даже еслинаписанные с одинаковыми жетонами, никогда не считаются эквивалентными.(Но если одно и то же выражение из определения какого-либо одного понятия используется несколькими способами посредством «расширения» определений понятий, это выражение всегда должно рассматриваться как имеющее непротиворечивое логическое значение.)

Если использовать эти упрощенные правила, можно показать (например, используя таблицу истинности), что удовлетворение ограничения X подразумевает ограничение Y, также должно быть выполнено, мы говорим, что X "включает" Y.И если два ограниченных шаблона функции или частичная специализация шаблона класса будут эквивалентны, а их ограничения игнорируются, а объединенное ограничение шаблона № 1 объединяет объединенное ограничение шаблона № 2 (но не наоборот), это еще один способ получения шаблона.№ 1 считается «более специализированным, чем» шаблон № 2.

Поэтому, сравнивая ваши Vec и VecInt, C ++ знает, что Vec<T> означает is_vector<T>::value, а VecInt<T> означает is_vector_of_int<T>::value, но на этом останавливается и не пытается найти какие-либо логические отношения между этимидва выражения.Таким образом, ни одна концепция не объединяет другую, и ни один шаблон, использующий эти концепции, не является более специализированным, чем другой, что может привести к неоднозначному вызову перегрузки.

При сравнении ваших Vec и моего VecInt, C ++ не 'Попробуйте определить, что означает std::is_same_v<typename T::value_type, int>.Но поскольку Vec<T> && anything true означает, что Vec<T> также верно, C ++ знает, что VecInt включает Vec, и поэтому func # 1 более специализирован, чем func # 2.

хотя концепции и ограничения, безусловно, являются полезным и полезным дополнением к языку, я думаю, что установление связей между понятиями как можно более сильными приведет к различию между концепциями, которые работают достаточно хорошо для конкретных целей, и действительно хорошими концепциями общего назначения для библиотечного уровня.Выполнение последнего будет непростым делом (и абсолютно обязательно потребует общего набора многих базовых стандартных концепций), и я ожидаю, что сообществу C ++ потребуется изучить некоторые правила «наилучшей практики», как делать это вместе по ходу работы.

0 голосов
/ 26 июня 2018

Код гольф!Это короче:

template<class, template<class...> class>
inline constexpr bool is_specialization = false;
template<template<class...> class T, class... Args>
inline constexpr bool is_specialization<T<Args...>, T> = true;

template<class T>
concept bool Vec = is_specialization<T, std::vector>;

template<class T>
concept bool VecInt = Vec<T> && 
  std::is_same_v<int, typename T::value_type>;

Имеет предполагаемое поведение (https://wandbox.org/permlink/iZpUZRC5s73co0bV), и черта is_specialization может использоваться повторно с любым шаблоном класса, который принимает только параметры типа.

0 голосов
/ 26 июня 2018
template<class T>
struct tag_t{using type=T;};
template<class T>
constexpr tag_t<T> tag{};
template<template<class...>class Z>
struct ztemplate_t{
  template<class...Ts>
  constexpr auto operator()(tag_t<Ts>...)const{ return tag<Z<Ts...>>; } // does not work in some modern compilers
};
template<template<class...>class Z>
constexpr ztemplate_t<Z> ztemplate{};

template<class...Ts>
constexpr std::false_type is(Ts...){return {};}
template<class...Us, template<class...>class, class...Ts>
constexpr std::true_type is( tag_t<Z<Ts...,Us...>>, ztemplate_t<Z>, tag_t<Ts>... ){ return {};}

нормально, шаблон готов.

template<class T>
constexpr auto is_vector = is( tag<T>, ztemplate<std::vector> );
template<class T>
constexpr auto is_vector_int = is( tag<T>, ztemplate<std::vector>, tag<int> );
0 голосов
/ 26 июня 2018

У вас уже может быть более короткий код путем повторного использования std::true_type / std::false_type:

template <class T>
struct is_vector : std::false_type {};

template <class T, class A>
struct is_vector<std::vector<T, A>> : std::true_type {};

template <class T>
struct is_vector_of_int : std::false_type {};

template <class A>
struct is_vector_of_int<std::vector<int, A>> : std::true_type {};

Не уверен, что вы можете использовать более короткий код.

...