Данный :

#include <concepts>
#include <iostream>

template<class T>
struct wrapper;

template<std::signed_integral T>
struct wrapper<T>
{
    wrapper() = default;
    void print()
    {
        std::cout << "signed_integral" << std::endl;
    }
};

template<std::integral T>
struct wrapper<T>
{
    wrapper() = default;
    void print()
    {
        std::cout << "integral" << std::endl;
    }
};

int main()
{
    wrapper<int> w;
    w.print(); // Output : signed_integral
    return 0;
}

Из приведенного выше кода int соответствует как std::integral, так и std::signed_integral концепции.

Удивительно, но это компилирует и печатает «signature_integral» на компиляторах GCC и MSVC. Я ожидал, что он потерпит неудачу с ошибкой в духе «специализация шаблона уже определена».

Хорошо, это законно, достаточно справедливо, но почему std::signed_integral был выбран вместо std::integral? Существуют ли какие-либо правила, определенные в стандарте, с помощью которых выбирается специализация шаблона, когда несколько аргументов соответствуют критериям шаблона?

25
Lewis Liman 28 Янв 2020 в 19:06

3 ответа

Лучший ответ

Это связано с тем, что концепции могут быть более специализированными, чем другие, что-то вроде порядка шаблонов. Это называется частичное упорядочение ограничений

В случае понятий они объединяют друг друга, когда включают эквивалентные ограничения. Например, вот как реализованы std::integral и std::signed_integral:

template<typename T>
concept integral = std::is_integral_v<T>;

template<typename T> //   v--------------v---- Using the contraint defined above
concept signed_integral = std::integral<T> && std::is_signed_v<T>;

Нормализуя ограничения, компилятор сводит выражение ограничения к следующему:

template<typename T>
concept integral = std::is_integral_v<T>;

template<typename T>
concept signed_integral = std::is_integral_v<T> && std::is_signed_v<T>;

В этом примере signed_integral подразумевает integral полностью. Таким образом, в некотором смысле подписанный интеграл «более ограничен», чем интеграл.

Стандарт пишет это так:

Из [temp.func.order] / 2 (выделено мной) :

Частичное упорядочение выбирает, какой из двух шаблонов функций больше специализируется, чем другой, преобразовывая каждый шаблон по очереди (см. следующий абзац) и выполняя вывод аргумента шаблона, используя тип функции Процесс вывода определяет, является ли один из шаблонов более специализированным, чем другой. Если это так, то более специализированный шаблон выбирается в процессе частичного заказа. Если оба вывода успешны, частичное упорядочение выбирает более ограниченный шаблон, как описано в правилах в [temp.constr.order] / 1:

Ограничение P включает ограничение Q тогда и только тогда, когда для каждого дизъюнктивного предложения P i в дизъюнктивной норме форма P , P i включает каждое конъюнктивное предложение Q j в конъюнктиве нормальная форма Q , где

  • дизъюнктивное предложение P i включает в себя конъюнктивное предложение Q j тогда и только тогда, когда существует атомарный ограничение P ia в P i , для которого существует атомное ограничение Q jb в Q j , так что P ia включает Q jb и

  • атомарное ограничение A включает другое атомарное ограничение B тогда и только тогда, когда A и B идентичны, используя правила, описанные в [temp.constr.atomic].

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

14
Guillaume Racicot 28 Янв 2020 в 16:37

С Partial_ordering_of_constraints

Говорят, что ограничение P включает ограничение Q, если можно доказать, что P подразумевает Q с точностью до единичных атомных ограничений в P и Q.

А также

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

  • лучший жизнеспособный кандидат для не шаблонной функции в разрешении перегрузки
  • адрес не шаблонной функции в наборе перегрузки
  • лучшее совпадение для аргумента шаблона шаблона
  • частичное упорядочение специализаций шаблонов классов
  • частичное упорядочение шаблонов функций

И концепция std::signed_integral включает std::integral<T>:

template < class T >
concept signed_integral = std::integral<T> && std::is_signed_v<T>;

Итак, ваш код в порядке, так как std::signed_integral более "специализирован".

3
Jarod42 28 Янв 2020 в 16:20

C ++ 20 имеет механизм для принятия решения, когда одна конкретная ограниченная сущность «более ограничена», чем другая. Это не простая вещь.

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

Итак, давайте посмотрим, как integral и signed_integral концепции определены:

template<class T>
  concept integral = is_integral_v<T>;
template<class T>
  concept signed_­integral = integral<T> && is_signed_v<T>;

Разложение integral просто is_integral_v. Разложение signed_integral - is_integral_v && is_signed_v.

Теперь мы подошли к концепции подчинения ограничения. Это довольно сложно, но основная идея состоит в том, что ограничение C1, как говорят, «включает» ограничение C2, если разложение C1 содержит каждое подвыражение в C2. Мы можем видеть, что integral не включает signed_integral, но signed_integral делает включает integral, поскольку содержит все, что integral делает.

Далее мы подходим к упорядочению ограниченных объектов:

Объявление D1, по крайней мере, столь же ограничено, как и объявление D2, если * D1 и D2 оба являются ограниченными объявлениями, а связанные с D1 ограничения относятся к ограничениям D2; или * D2 не имеет связанных ограничений.

Поскольку signed_integral включает integral, <signed_integral> wrapper "по крайней мере так же ограничен", как и <integral> wrapper. Тем не менее, обратное неверно из-за необратимого включения.

Следовательно, в соответствии с правилом для «более ограниченных» объектов:

Объявление D1 более ограничено, чем другое объявление D2, когда D1 по крайней мере так же ограничено, как D2, а D2 не по крайней мере так же ограничено, как D1.

Так как <integral> wrapper не настолько ограничен, как <signed_integral> wrapper, последний считается более ограниченным, чем первый.

И поэтому, когда они оба могут подать заявку, побеждает более ограниченное объявление.


Помните, что правила подчинения ограничений stop при обнаружении выражения, которое не является concept. Итак, если вы сделали это:

template<typename T>
constexpr bool my_is_integral_v = std::is_integral_v<T>;

template<typename T>
concept my_signed_integral = my_is_integral_v<T> && std::is_signed_v<T>;

В этом случае my_signed_integral не будет включать std::integral. Несмотря на то что my_is_integral_v определен идентично std::is_integral_v, поскольку это не концепция, правила подстановки в C ++ не могут просмотреть его, чтобы определить, что они одинаковые.

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

11
Nicol Bolas 28 Янв 2020 в 16:31