Концепции C ++: проверка экземпляра шаблона

Предполагая, что у меня есть шаблонный тип, например

template<typename A, typename B, typename C>
struct mytype { };

Как мне написать концепцию, которая проверяет, является ли тип экземпляром этого шаблона?

template<typename T>
concept MyType = requires(T x) { ??? }

Я не могу придумать очевидный способ сделать это, не обращаясь к специализированным типам детекторов старого стиля или, возможно, к базовому типу маркера.


person MrMobster    schedule 14.01.2019    source источник
comment
не уверен, что именно вы спрашиваете. Концепции еще нет в стандарте, или я это пропустил?   -  person 463035818_is_not_a_number    schedule 14.01.2019
comment
Они находятся в черновике C ++ 20, поэтому, конечно, мой вопрос основан на текущем черновике   -  person MrMobster    schedule 14.01.2019
comment
Какая от этого польза?   -  person cpplearner    schedule 14.01.2019
comment
@cpplearner Одним из таких вариантов использования может быть идеальная пересылка только для аргументов определенного типа. Лучше концепты, чем SFINAE :)   -  person Daniel Langr    schedule 14.01.2019
comment
@cpplearner Прямое и очевидное использование - это обеспечение безопасности типов без необходимости повторять обычный многословный танец шаблонов. То есть вместо template<typename A, typename B, typename C> void do_stuff(mytype<A, B, C>) вы можете просто template<MyType T> do_stuff(T). И, надеюсь, однажды do_stuff(MyType T), как и предполагал Страуструп.   -  person MrMobster    schedule 14.01.2019
comment
@MrMobster: На самом деле концепции не в этом. Вы говорите о том, чтобы сделать конкретный сценарий использования более удобным. Концепции заключаются в том, чтобы найти способ выразить ограничения возможностей одного или нескольких аргументов шаблона. То есть вы не ограничиваете шаблон конкретным типом; вы ограничиваете шаблон конкретным интерфейсом, которому могут (теоретически) соответствовать несколько типов. Использование такого механизма только для того, чтобы вам не пришлось набирать несколько лишних слов, - не главное.   -  person Nicol Bolas    schedule 15.01.2019
comment
@NicolBolas Я говорю о безопасности типов. И в этом суть концепции - исправление огромного пробела в системе типов C ++ - того факта, что шаблоны не являются настоящими типами, а вместо этого являются макроподобными конструкциями, которые полагаются на утиную типизацию. Конечно, главное преимущество концепций заключается в том, что они обеспечивают безопасность типов вокруг общего API (сродни тому, что протоколы и трейты делают в Swift и Rust), но я также не вижу причин, по которым их не следует использовать для обеспечения безопасности типов вокруг единственной реализации. параметризованного типа.   -  person MrMobster    schedule 15.01.2019
comment
@MrMobster: Этого не должно быть, потому что вы уже можете это сделать, о чем свидетельствует тот факт, что вы написали первую версию. do_stuff(MyType auto T) - это гораздо более тупое написание того, что вы действительно хотите, поскольку вам нужно отследить шаблон MyType, чтобы понять, что он означает создание экземпляра mytype. Я считаю, что это ничем не отличается от решения заменить my_func(int i) на my_func(Int auto i) или что-то подобное.   -  person Nicol Bolas    schedule 15.01.2019
comment
@NicolBolas, чтобы вы утверждали, что повторение всего набора параметров шаблона (которые совершенно не имеют отношения к алгоритму) каждый раз приводит к получению лучшего кода, чем один спецификатор типа? И, конечно же, я согласен с тем, что между mytype и MyType существует определенное логическое несоответствие, которого снова можно было бы избежать, если бы шаблоны были правильными параметризованными типами в системе ...   -  person MrMobster    schedule 15.01.2019
comment
@MrMobster: Код лучше? да. Во-первых, он позволяет избежать описанного мною разъединения. Но, что не менее важно, во-вторых, это заставляет вас остановиться и спросить ... если do_stuff не использует эти параметры шаблона, почему я хочу, чтобы моя функция шаблона do_stuff принимала только mytype создание экземпляра? Почему бы мне не написать правильный концептуальный интерфейс, против которого do_stuff можно было бы писать? Тот, который не явно зависит от самого mytype? Не похоже, что range::sort написано специально против vector; он написан против любого диапазона произвольного доступа; vector просто предоставляет этот интерфейс.   -  person Nicol Bolas    schedule 15.01.2019
comment
Связано: stackoverflow.com/questions/17390605/   -  person ecatmur    schedule 19.02.2021


Ответы (5)


Используя вывод аргументов шаблона класса C ++ 17, вы сможете сделать что-то вроде этого:

template<typename A, typename B, typename C>
struct mytype { };

template<class T>
concept C1 = requires(T x) { 
    { mytype{x} } -> std::same_as<T>;
};

mytype{x} использует вывод аргументов шаблона класса для вывода A, B и C, так что это допустимо, если вы можете построить mytype<A, B, C> из T. В частности, это действительно, если mytype может быть сконструирован путем копирования, поскольку у вас есть неявно объявленный руководство по копированию, похожее на:

template <typename A, typename B, typename C>
mytype(mytype<A, B, C>) -> mytype<A, B, C>;

Проверяя, что T также является созданным экземпляром mytype, избегайте сопоставления с другими руководствами по дедукции, например, это будет соответствовать любому типу без -> std::same_as<T>:

template <class A, class B, class C>
struct mytype {
    mytype(A);
};

template <class A>
mytype(A) -> mytype<A, A, A>;

Предлагаемое решение не работает для классов, не поддерживающих копирование, хотя должно быть возможно заставить его работать для классов, предназначенных только для перемещения.


Протестировано с:

person Holt    schedule 14.01.2019
comment
Но кто сказал, что A, B и C могут быть выведены таким образом? - person einpoklum; 14.01.2019
comment
Вы имеете в виду руководство по дедукции, которое сможет вывести A, B и C именно для тех типов T, которые являются экземплярами шаблона? - person einpoklum; 15.01.2019
comment
Я понятия не имею, почему кто-то проголосовал против вашего ответа - это правильно и точно отвечает на мой вопрос. - person MrMobster; 15.01.2019
comment
@einpoklum Предполагая, что mytype имеет не удаленный конструктор копирования, у вас есть неявно сгенерированное руководство по дедукции, подобное: template <class A, class B, class C> mystruct(mystruct<A, B, C> const&) -> mystruct<A, B, C>;, которое является руководством по копированию. - person Holt; 15.01.2019
comment
Это решение не соответствует стандарту C ++ 20. Требование типа возвращаемого значения в выражении requires должно быть концепцией, а не типом. Замените - ›T на -› std :: same_as ‹T›. - person Björn Sundin; 28.12.2020

Для этой цели вы можете определить свою собственную мета-функцию (признак типа):

template <typename T>
struct is_mytype : std::false_type { };

template <typename A, typename B, typename C>
struct is_mytype<mytype<A, B, C>> : std::true_type { };

template <typename T>
concept MyType = is_mytype<T>::value;

Но, честно говоря, я не знаю, нет ли способа, как определить такую ​​концепцию напрямую, без необходимости в отдельной метафункции.

person Daniel Langr    schedule 14.01.2019
comment
Да, это тоже было бы моим решением. Я надеялся, что концепции сделают такие детекторы устаревшими. - person MrMobster; 14.01.2019
comment
@MrMobster Да ... как только вы их написали. Хе-хе. - person Lightness Races in Orbit; 15.01.2019

Вы можете написать обобщенный признак для проверки специализаций:

template <typename T, template <typename...> class Z>
struct is_specialization_of : std::false_type {};

template <typename... Args, template <typename...> class Z>
struct is_specialization_of<Z<Args...>, Z> : std::true_type {};

template <typename T, template <typename...> class Z>
inline constexpr bool is_specialization_of_v = is_specialization_of<T,Z>::value;

Что вы можете превратить в обобщенную концепцию:

template<typename T, template <typename...> class Z>
concept Specializes = is_specialization_of_v<T, Z>;

template<typename T>
concept MyType = Specializes<T, mytype>;

или просто специализированный:

template<typename T>
concept MyType = is_specialization_of_v<T, mytype>;
person Barry    schedule 15.01.2019
comment
Это не совсем обобщенный подход, поскольку он работает только с классами шаблонов, которые сами принимают типы. Поэтому вы не можете спросить, является ли это специализацией std::array или даже std::span, поскольку они принимают не типовые параметры шаблона. - person Nicol Bolas; 15.01.2019
comment
@NicolBolas Однако подавляющее большинство шаблонов принимают только параметры типа. Так что я бы сказал, что это довольно обобщенно. Но да, с этими двумя это не сработает. - person Barry; 15.01.2019

В интересах краткости:

template<typename T>
concept MyType = requires(T** x) {
    []<typename A, typename B, typename C>(mytype<A, B, C>**){}(x);
};

Двойной указатель необходим, чтобы избежать преобразований производных в базовые, например. struct S : mytype<int, int, int> {}.

В настоящее время это не работает в clang, поскольку не позволяет использовать лямбды в неоцененном контексте. Вы можете обойти эту проблему, предоставив шаблон вспомогательной переменной:

template<class T> constexpr auto L = []<typename A, typename B, typename C>(mytype<A, B, C>**){};
template<typename T>
concept MyType = requires(T** x) { L<T>(x); };

Пока аргументы шаблона mytype являются всеми типами, вы можете сделать это еще более кратким, используя заполнители:

template<typename T>
concept MyType = requires(T** x) { [](mytype<auto, auto, auto>**){}(x); };

В настоящее время это работает только в gcc.

person ecatmur    schedule 19.02.2021
comment
Молодец, спасибо! Я использовал последний, но только с одиночными указателями, потому что я хочу иметь возможность обнаруживать производные классы шаблонного типа. Это на 14 строк кода меньше. \ o / - person underscore_d; 22.03.2021

Если вы наделили свой шаблонный класс некоторыми чертами, вы можете сделать следующее:

template<typename A, typename B, typename C>
struct mytype {
    using a_type = A;
    using b_type = B;
    using c_type = C;
};

С ассоциированной концепцией:

template <typename T>
concept is_mytype =
    std::is_same_v<
        std::remove_const_t<T>,
        mytype<typename T::a_type, typename T::b_type, typename T::c_type>>;

Или, если у mytype есть члены этих типов, вы можете пропустить черты:

template<typename A, typename B, typename C>
struct mytype {
    A a_inst;
    B b_inst;
    C c_inst;
};

Придание концепции:

template <typename T>
concept is_mytype =
    std::is_same_v<
        std::remove_const_t<T>,
        mytype<decltype(T::a_inst), decltype(T::b_inst), decltype(T::c_inst)>>;
person JGLA    schedule 05.10.2020