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

Дано:

#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" на обоих G CC и MSV C компиляторы. Я ожидал, что он потерпит неудачу с ошибкой в ​​духе «специализация шаблона уже определена».

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

Ответы [ 3 ]

14 голосов
/ 28 января 2020

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

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

Стандарт записывает это так:

From [temp.fun c .order] / 2 (выделение мое):

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

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

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

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

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

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

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

11 голосов
/ 28 января 2020

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

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

Итак, давайте посмотрим, как определяются понятия 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.

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

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

3 голосов
/ 28 января 2020

С Partial_ordering_of_constraints

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

и

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

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

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

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

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

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