C ++ 20 Основные понятия: какая специализация шаблона выбирается, когда аргумент шаблона соответствует нескольким понятиям?

23

Данный :

#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? Существуют ли какие-либо правила, определенные в стандарте, с помощью которых выбирается специализация шаблона, когда несколько аргументов соответствуют критериям шаблона?

Льюис Лиман
источник
Я бы не сказал, что это законно только потому, что компиляторы принимают его, особенно на ранних этапах его принятия.
Слава
@ Слава, в данном случае, концепции тщательно продуманы, поэтому они интуитивно объединяют друг друга
Гийом Расикот
@ GuillaumeRacicot это нормально, я только что прокомментировал, что вывод «это законно, потому что компилятор принял это», допустим, вводит в заблуждение. Я не сказал, что это не законно, хотя.
Слава

Ответы:

14

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

В случае понятий они объединяют друг друга, когда включают эквивалентные ограничения. Например, вот как 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] .

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

Из [temp.constr.order] / 1 :

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

  • дизъюнктивное положение Р я вбирает конъюнктивной раздел Q J тогда и только тогда , когда существует атомный ограничение P IA в P я , для которого существует ограничение атомного Q JB в Q J такое , что P IA вбирает Q Jb , и

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

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

Гийом Расико
источник
2
Похоже, вы затихли в середине абзаца ...
ShadowRanger
11

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, последнее считается более ограниченным, чем первое.

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


Имейте в виду, что правила подчинения ограничения прекращаются, когда встречается выражение, которое не является 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 ++ не могут просмотреть его, чтобы определить, что они одинаковы.

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

Николь Болас
источник
3

С 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он более «специализирован».

Jarod42
источник