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поняття.

Дивно, але цей компілює та друкує "підписаний_інтеграл" на компіляторах GCC та MSVC. Я очікував, що це не вдасться із помилкою за рядками "вже визначена спеціалізація шаблонів".

Гаразд, це законно, досить справедливо, але чому його std::signed_integralобрали замість std::integral? Чи є якісь правила, визначені у стандарті, з якою спеціалізацією шаблону вибирається, коли для аргументу шаблону відповідають декілька понять?


Я б не сказав, що це законно лише тим, що компілятор (и) приймає його, особливо на ранніх етапах його прийняття.
Слава

@Slava в даному випадку це так, концепції ретельно розроблені, щоб вони інтуїтивно зрозуміли один одного
Гійом Рачикот,

@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 , де

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

  • атомне обмеження A підпадає під інше атомне обмеження B тоді і лише тоді, коли A і B є тотожними, використовуючи правила, описані в [temp.constr.atomic] .

Це описує алгоритм заміщення, який компілятор використовує для впорядкування обмежень, а отже і понять.


2
Схоже, ви
відстали

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, останнє вважається більш обмеженим, ніж перше.

І тому, коли вони обидва можуть подати заявку, виграє більш обмежена декларація.


Майте на увазі, що правила обмеження обмежень припиняються, коли виникає вираз, який не є a 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

З частковими_ординарними_оборонами

Кажуть, що обмеження 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і більш "спеціалізований".

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.