Namespaces
Variants

Constraints and concepts

From cppreference.net


На этой странице описывается экспериментальная возможность основного языка. Для именованных требований к типам, используемых в спецификации стандартной библиотеки, см. named requirements

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

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

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

#include <string>
#include <locale>
using namespace std::literals;
// Объявление концепта "EqualityComparable", который удовлетворяется
// любым типом T, для которого для значений a и b типа T
// выражение a==b компилируется и его результат преобразуется в bool
template<typename T>
concept bool EqualityComparable = requires(T a, T b) {
    { a == b } -> bool;
};
void f(EqualityComparable&&); // объявление ограниченного шаблона функции
// template<typename T>
// void f(T&&) requires EqualityComparable<T>; // длинная форма того же
int main() {
  f("abc"s); // OK, std::string является EqualityComparable
  f(std::use_facet<std::ctype<char>>(std::locale{})); // Ошибка: не является EqualityComparable 
}

Нарушения ограничений обнаруживаются на этапе компиляции, в начале процесса инстанцирования шаблона, что приводит к понятным сообщениям об ошибках.

std::list<int> l = {3,-1,10};
std::sort(l.begin(), l.end()); 
//Типичная диагностика компилятора без концепций:
//  invalid operands to binary expression ('std::_List_iterator<int>' and
//  'std::_List_iterator<int>')
//                           std::__lg(__last - __first) * 2);
//                                     ~~~~~~ ^ ~~~~~~~
// ... 50 строк вывода ...
//
//Типичная диагностика компилятора с концепциями:
//  error: cannot call std::sort with std::_List_iterator<int>
//  note:  concept RandomAccessIterator<std::_List_iterator<int>> was not satisfied

Цель концепций — моделировать семантические категории (Number, Range, RegularFunction), а не синтаксические ограничения (HasPlus, Array). Согласно ISO C++ core guideline T.20 , "Возможность задать осмысленную семантику является определяющей характеристикой истинного концепта в отличие от синтаксического ограничения."

Если тестирование возможностей поддерживается, возможности, описанные здесь, обозначаются макроконстантой __cpp_concepts со значением равным или большим 201507 .

Содержание

Заполнители

Неограниченный заполнитель auto и ограниченные заполнители , имеющие форму concept-name < template-argument-list (optional) > , являются заполнителями для типа, который должен быть выведен.

Заполнители могут появляться в объявлениях переменных (в этом случае они выводятся из инициализатора) или в возвращаемых типах функций (в этом случае они выводятся из return-выражений)

std::pair<auto, auto> p2 = std::make_pair(0, 'a'); // первый auto - это int,
                                                   // второй auto - это char
Sortable x = f(y); // тип x выводится из возвращаемого типа f, 
                   // компилируется только если тип удовлетворяет ограничению Sortable
auto f(Container) -> Sortable; // возвращаемый тип выводится из оператора return
                               // компилируется только если тип удовлетворяет Sortable

Заполнители также могут появляться в параметрах, в этом случае они превращают объявления функций в объявления шаблонов (ограниченные, если заполнитель ограничен)

void f(std::pair<auto, EqualityComparable>); // это шаблон с двумя параметрами:
       // неограниченный параметр типа и ограниченный параметр не-типа

Ограниченные заполнители могут использоваться везде, auto может быть использован, например, в объявлениях обобщенных лямбда-выражений

auto gl = [](Assignable& a, auto* b) { a = *b; };

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

template<size_t N> concept bool Even = (N%2 == 0);
struct S1 { int n; };
int Even::* p2 = &S1::n; // ошибка, недопустимое использование нетипового концепта
void f(std::array<auto, Even>); // ошибка, недопустимое использование нетипового концепта
template<Even N> void f(std::array<auto, N>); // корректно

Сокращённые шаблоны

Если один или несколько заполнителей появляются в списке параметров функции, объявление функции фактически является объявлением шаблона функции, чей список параметров шаблона включает один изобретенный параметр для каждого уникального заполнителя, в порядке их появления

// краткая форма
void g1(const EqualityComparable*, Incrementable&);
// полная форма:
// template<EqualityComparable T, Incrementable U> void g1(const T*, U&);
// расширенная форма:
// template<typename T, typename U>
// void g1(const T*, U&) requires EqualityComparable<T> && Incrementable<U>;
void f2(std::vector<auto*>...);
// полная форма: template<typename... T> void f2(std::vector<T*>...);
void f4(auto (auto::*)(auto));
// полная форма: template<typename T, typename U, typename V> void f4(T (U::*)(V));

Все заполнители, введенные эквивалентными ограниченными спецификаторами типа, имеют один и тот же вымышленный параметр шаблона. Однако каждый неограниченный спецификатор ( auto ) всегда вводит другой параметр шаблона

void f0(Comparable a, Comparable* b);
// длинная форма: template<Comparable T> void f0(T a, T* b);
void f1(auto a, auto* b);
// длинная форма: template<typename T, typename U> f1(T a, U* b);

Как шаблоны функций, так и шаблоны классов могут быть объявлены с использованием введения шаблона , которое имеет синтаксис concept-name { parameter-list (optional) } , в этом случае ключевое слово template не требуется: каждый параметр из parameter-list введения шаблона становится параметром шаблона, тип которого (тип, не-тип, шаблон) определяется типом соответствующего параметра в указанной концепции.

Помимо объявления шаблона, введение шаблона связывает предикатное ограничение (см. ниже), которое именует (для концептов переменных) или вызывает (для концептов функций) концепт, указанный введением.

EqualityComparable{T} class Foo;
// длинная форма: template<EqualityComparable T> class Foo;
// расширенная форма: template<typename T> requires EqualityComparable<T> class Foo;
template<typename T, int N, typename... Xs> concept bool Example = ...;
Example{A, B, ...C} struct S1;
// длинная форма template<class A, int B, class... C> requires Example<A,B,C...> struct S1;

Для шаблонов функций введение шаблона может быть объединено с заполнителями:

Sortable{T} void f(T, auto);
// длинная форма: template<Sortable T, typename U> void f(T, U);
// альтернатива с использованием только заполнителей: void f(Sortable, auto);

Концепции

Концепт — это именованный набор требований. Определение концепта располагается в области видимости пространства имён и имеет форму определения шаблона функции (в этом случае он называется функциональным концептом ) или определения шаблона переменной (в этом случае он называется переменным концептом ). Единственное различие заключается в том, что ключевое слово concept появляется в последовательности-спецификаторов-объявления :

// концепция переменной из стандартной библиотеки (Ranges TS)
template <class T, class U>
concept bool Derived = std::is_base_of<U, T>::value;
// концепция функции из стандартной библиотеки (Ranges TS)
template <class T>
concept bool EqualityComparable() { 
    return requires(T a, T b) { {a == b} -> Boolean; {a != b} -> Boolean; };
}

Следующие ограничения применяются к функциональным концепциям:

  • inline и constexpr не допускаются, функция автоматически является inline и constexpr
  • friend и virtual не допускаются
  • спецификация исключений не допускается, функция автоматически является noexcept(true) .
  • не может быть объявлена и определена позже, не может быть переобъявлена
  • возвращаемый тип должен быть bool
  • выведение возвращаемого типа не допускается
  • список параметров должен быть пустым
  • тело функции должно состоять только из оператора return , чей аргумент должен быть constraint-expression (предикатное ограничение, конъюнкция/дизъюнкция других ограничений или requires-выражение, см. ниже)

К переменным концептам применяются следующие ограничения:

  • Должен иметь тип bool
  • Не может быть объявлен без инициализатора
  • Не может быть объявлен на уровне класса
  • constexpr не допускается, переменная автоматически является constexpr
  • инициализатор должен быть выражением ограничения (ограничение-предикат, конъюнкция/дизъюнкция ограничений или requires-выражение, см. ниже)

Концепты не могут рекурсивно ссылаться на себя в теле функции или в инициализаторе переменной:

template<typename T>
concept bool F() { return F<typename T::type>(); } // ошибка
template<typename T>
concept bool V = V<T*>; // ошибка

Явные инстанцирования, явные специализации или частичные специализации концептов не допускаются (смысл исходного определения ограничения не может быть изменён)

Ограничения

Ограничение — это последовательность логических операций, определяющая требования к аргументам шаблона. Они могут появляться внутри requires-выражений (см. ниже) и непосредственно в качестве тел концепций

Существует 9 типов ограничений:

1) союзы
2) дизъюнкции
3) ограничения предикатов
4) ограничения выражений (только в requires-выражении )
5) ограничения типов (только в requires-выражении )
6) ограничения неявного преобразования (только в requires-выражении )
7) ограничения вывода аргументов (только в requires-выражении )
8) ограничения исключений (только в requires-expression )
9) параметризованные ограничения (только в requires-expression )

Первые три типа ограничений могут появляться непосредственно в теле концепции или в виде специального requires-выражения:

template<typename T>
requires // requires-выражение (ad-hoc ограничение)
sizeof(T) > 1 && get_value<T>() // конъюнкция двух предикатных ограничений
void f(T);

Когда несколько ограничений присоединены к одному объявлению, общее ограничение представляет собой конъюнкцию в следующем порядке: ограничение, введённое шаблонным введением , ограничения для каждого параметра шаблона в порядке их появления, requires-выражение после списка параметров шаблона, ограничения для каждого параметра функции в порядке их появления, завершающее requires-выражение :

// объявления объявляют один и тот же ограниченный шаблон функции
// с ограничением Incrementable<T> && Decrementable<T>
template<Incrementable T> void f(T) requires Decrementable<T>;
template<typename T> requires Incrementable<T> && Decrementable<T> void f(T); // корректно
// следующие два объявления имеют разные ограничения:
// первое объявление имеет Incrementable<T> && Decrementable<T>
// второе объявление имеет Decrementable<T> && Incrementable<T>
// Хотя они логически эквивалентны.
// Второе объявление некорректно, диагностика не требуется
template<Incrementable T> requires Decrementable<T> void g();
template<Decrementable T> requires Incrementable<T> void g(); // ошибка

Союзы

Конъюнкция ограничений P и Q задаётся как P && Q .

// пример концепций из стандартной библиотеки (Ranges TS)
template <class T>
concept bool Integral = std::is_integral<T>::value;
template <class T>
concept bool SignedIntegral = Integral<T> && std::is_signed<T>::value;
template <class T>
concept bool UnsignedIntegral = Integral<T> && !SignedIntegral<T>;

Конъюнкция двух ограничений удовлетворяется только если оба ограничения удовлетворены. Конъюнкции вычисляются слева направо с использованием сокращённого вычисления (если левое ограничение не удовлетворено, подстановка аргументов шаблона в правое ограничение не выполняется: это предотвращает ошибки, возникающие при подстановке вне непосредственного контекста). Пользовательские перегрузки operator&& не допускаются в конъюнкциях ограничений.

Дизъюнкции

Дизъюнкция ограничений P и Q задаётся как P || Q .

Дизъюнкция двух ограничений удовлетворяется, если удовлетворяется любое из ограничений. Дизъюнкции вычисляются слева направо с коротким замыканием (если левое ограничение удовлетворено, подстановка аргументов шаблона в правое ограничение не выполняется). Пользовательские перегрузки operator|| не разрешены в дизъюнкциях ограничений.

// пример ограничения из стандартной библиотеки (Ranges TS)
template <class T = void>
requires EqualityComparable<T>() || Same<T, void>
struct equal_to;

Ограничения предикатов

Предикатное ограничение — это константное выражение типа bool . Оно выполняется только в том случае, если вычисляется в true

template<typename T> concept bool Size32 = sizeof(T) == 4;

Предикатные ограничения могут задавать требования для нетиповых параметров шаблона и для аргументов шаблона-шаблона.

Ограничения предикатов должны вычисляться непосредственно в bool , преобразования не допускаются:

template<typename T> struct S {
    constexpr explicit operator bool() const { return true; }
};
template<typename T>
requires S<T>{} // некорректное ограничение-предикат: S<T>{} не является bool
void f(T);
f(0); // ошибка: ограничение никогда не выполняется

Требования

Ключевое слово requires используется двумя способами:

1) Ввести requires-выражение , которое задаёт ограничения на аргументы шаблона или на объявление функции.
template<typename T>
void f(T&&) requires Eq<T>; // может появляться как последний элемент декларатора функции
template<typename T> requires Addable<T> // или сразу после списка параметров шаблона
T add(T a, T b) { return a + b; }
В данном случае ключевое слово requires должно сопровождаться некоторым константным выражением (так что можно написать "requires true;"), но предполагается, что будет использоваться именованный концепт (как в примере выше) или конъюнкция/дизъюнкция именованных концептов, либо requires-выражение .
2) Для начала requires-выражения , которое является prvalue-выражением типа bool , описывающим ограничения на некоторые аргументы шаблона. Такое выражение равно true , если соответствующий концепт удовлетворён, и false в противном случае:
template<typename T>
concept bool Addable = requires (T x) { x + x; }; // requires-expression
template<typename T> requires Addable<T> // requires-clause, not requires-expression
T add(T a, T b) { return a + b; }
template<typename T>
requires requires (T x) { x + x; } // ad-hoc constraint, note keyword used twice
T add(T a, T b) { return a + b; }

Синтаксис requires-expression выглядит следующим образом:

requires ( список-параметров (опционально) ) { последовательность-требований }
parameter-list - список параметров, разделенных запятыми, как в объявлении функции, за исключением того, что аргументы по умолчанию не допускаются и последний параметр не может быть многоточием. Эти параметры не имеют хранилища, связывания или времени жизни. Эти параметры находятся в области видимости до закрывающей скобки } последовательности requirement-seq . Если параметры не используются, круглые скобки также могут быть опущены.
requirement-seq - последовательность требований , разделенных пробелами, описана ниже (каждое требование заканчивается точкой с запятой). Каждое требование добавляет еще одно ограничение к конъюнкции ограничений, которую определяет это requires-выражение.

Каждое требование в requirements-seq является одним из следующих:

  • простое требование
  • требования к типу
  • составное требование
  • вложенное требование

Требования могут ссылаться на параметры шаблона, находящиеся в области видимости, и на локальные параметры, введенные в parameter-list . При параметризации считается, что requires-выражение вводит параметризованное ограничение

Подстановка аргументов шаблона в requires-выражение может привести к формированию некорректных типов или выражений в его требованиях. В таких случаях,

  • Если в requires-выражении, используемом вне объявления шаблонной сущности , происходит ошибка подстановки, то программа является некорректной.
  • Если requires-выражение используется в объявлении шаблонной сущности , соответствующее ограничение рассматривается как "не выполненное" и ошибка подстановки не является ошибкой , однако
  • Если ошибка подстановки происходит в requires-выражении для каждого возможного аргумента шаблона, программа является некорректной, диагностика не требуется:
template<class T> concept bool C = requires {
    new int[-(int)sizeof(T)]; // недопустимо для любого T: некорректно сформировано, диагностика не требуется
};

Простые требования

Простое требование представляет собой произвольное выражение. Требование заключается в том, что выражение является корректным (это ограничение-выражение ). В отличие от ограничений-предикатов, вычисление не производится, проверяется только синтаксическая корректность.

template<typename T>
concept bool Addable =
requires (T a, T b) {
    a + b; // "выражение a+b является корректным выражением, которое скомпилируется"
};
// пример ограничения из стандартной библиотеки (ranges TS)
template <class T, class U = T>
concept bool Swappable = requires(T&& t, U&& u) {
    swap(std::forward<T>(t), std::forward<U>(u));
    swap(std::forward<U>(u), std::forward<T>(t));
};

Требования к типам

Требование типа — это ключевое слово typename , за которым следует имя типа, опционально квалифицированное. Требование заключается в том, что указанный тип существует ( ограничение типа ): это может использоваться для проверки существования определённого именованного вложенного типа, что специализация шаблона класса обозначает тип, или что шаблон псевдонима обозначает тип.

template<typename T> using Ref = T&;
template<typename T> concept bool C =
requires {
    typename T::inner; // требуется вложенное имя члена
    typename S<T>;     // требуется специализация шаблона класса
    typename Ref<T>;   // требуется подстановка шаблона псевдонима
};
// Пример концепции из стандартной библиотеки (Ranges TS)
template <class T, class U> using CommonType = std::common_type_t<T, U>;
template <class T, class U> concept bool Common =
requires (T t, U u) {
    typename CommonType<T, U>; // CommonType<T, U> является валидным и обозначает тип
    { CommonType<T, U>{std::forward<T>(t)} }; 
    { CommonType<T, U>{std::forward<U>(u)} }; 
};

Составные требования

Составное требование имеет вид

{ expression } noexcept (необязательно) trailing-return-type (необязательно) ;

и задаёт конъюнкцию следующих ограничений:

1) expression является допустимым выражением ( expression constraint )
2) Если используется noexcept , выражение также должно быть noexcept ( ограничение исключений )
3) Если trailing-return-type указывает на тип, использующий заполнители, тип должен быть выводим из типа выражения ( ограничение вывода аргументов )
4) Если trailing-return-type указывает на тип, который не использует плейсхолдеры, то добавляются еще два ограничения:
4a) тип, указанный в trailing-return-type , является допустимым ( ограничение типа )
4b) результат выражения неявно преобразуем к этому типу ( ограничение неявного преобразования )
template<typename T> concept bool C2 =
requires(T x) {
    {*x} -> typename T::inner; // выражение *x должно быть валидным
                               // И тип T::inner должен быть валидным
                               // И результат *x должен быть конвертируемым в T::inner
};
// Пример концепции из стандартной библиотеки (Ranges TS)
template <class T, class U> concept bool Same = std::is_same<T,U>::value;
template <class B> concept bool Boolean =
requires(B b1, B b2) {
    { bool(b1) }; // ограничение прямой инициализации должно использовать выражение
    { !b1 } -> bool; // составное ограничение
    requires Same<decltype(b1 && b2), bool>; // вложенное ограничение, см. ниже
    requires Same<decltype(b1 || b2), bool>;
};

Вложенные требования

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

// пример ограничения из Ranges TS
template <class T>
concept bool Semiregular = DefaultConstructible<T> &&
    CopyConstructible<T> && Destructible<T> && CopyAssignable<T> &&
requires(T a, size_t n) {  
    requires Same<T*, decltype(&a)>;  // вложенное: "Same<...> вычисляется в true"
    { a.~T() } noexcept;  // составное: "a.~T()" является валидным выражением без исключений
    requires Same<T*, decltype(new T)>; // вложенное: "Same<...> вычисляется в true"
    requires Same<T*, decltype(new T[n])>; // вложенное
    { delete new T };  // составное
    { delete new T[n] }; // составное
};

Разрешение концепций

Как и любой другой шаблон функции, функциональное понятие (но не понятие-переменная) может быть перегружено: можно предоставить несколько определений понятия, которые все используют одно и то же concept-name .

Разрешение концепции выполняется, когда concept-name (который может быть квалифицированным) появляется в

1) ограниченный спецификатор типа void f ( Concept ) ; std:: vector < Concept > x = ... ;
2) ограниченный параметр template < Concept T > void f ( ) ;
3) введение шаблона Concept { T } struct X ;
4) a constraint-expression template < typename T > void f ( ) requires Concept < T > ;
template<typename T> concept bool C() { return true; } // #1
template<typename T, typename U> concept bool C() { return true; } // #2
void f(C); // набор концептов, на которые ссылается C, включает как #1, так и #2;
           // разрешение концептов (см. ниже) выбирает #1.

Для выполнения разрешения концепций, template parameters каждого концепта, соответствующего имени (и квалификации, если она есть), сопоставляются с последовательностью concept arguments , которые являются аргументами шаблона и wildcards . Wildcard может соответствовать параметру шаблона любого вида (тип, не-тип, шаблон). Набор аргументов конструируется по-разному в зависимости от контекста.

1) Для имени концепта, используемого как часть ограниченного спецификатора типа или параметра, если имя концепта используется без списка параметров, список аргументов представляет собой одиночный подстановочный знак.
template<typename T> concept bool C1() { return true; } // #1
template<typename T, typename U> concept bool C1() { return true; } // #2
void f1(const C1*); // <wildcard> matches <T>, selects #1
2) Для имени концепции, используемого как часть ограниченного спецификатора типа или параметра, если имя концепции используется со списком аргументов шаблона, список аргументов представляет собой одиночный подстановочный знак, за которым следует этот список аргументов.
template<typename T> concept bool C1() { return true; } // #1
template<typename T, typename U> concept bool C1() { return true; } // #2
void f2(C1<char>); // <wildcard, char> matches <T, U>, selects #2
3) Если концепция появляется в шаблоне введения, список аргументов представляет собой последовательность плейсхолдеров такой же длины, как список параметров в шаблоне введения
template<typename... Ts>
concept bool C3 = true;
C3{T} void q2();     // OK: <T> matches <...Ts>
C3{...Ts} void q1(); // OK: <...Ts> matches <...Ts>
4) Если концепт появляется в качестве имени шаблонного идентификатора, список аргументов концепта представляет собой точную последовательность аргументов этого шаблонного идентификатора
template<typename T> concept bool C() { return true; } // #1
template<typename T, typename U> concept bool C() { return true; } // #2
template <typename T>
void f(T) requires C<T>(); // matches #1

Разрешение концепций выполняется путем сопоставления каждого аргумента с соответствующим параметром каждого видимого концепта. Аргументы шаблона по умолчанию (если используются) инстанцируются для каждого параметра, который не соответствует аргументу, и затем добавляются к списку аргументов. Параметр шаблона соответствует аргументу только если он имеет тот же вид (тип, не-тип, шаблон), за исключением случая когда аргумент является wildcard. Параметр-пакет соответствует нулю или более аргументам при условии, что все аргументы соответствуют шаблону по виду (за исключением случаев когда они являются wildcard).

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

template<typename T> concept bool C2() { return true; }
template<int T> concept bool C2() { return true; }
template<C2<0> T> struct S1; // ошибка: <wildcard, 0> не соответствует 
                             // ни <typename T>, ни <int T>
template<C2 T> struct S2; // соответствуют и #1, и #2: ошибка

Частичное упорядочение ограничений

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

Концепт P называется субсумирующим концепт Q если можно доказать, что P влечёт Q без анализа типов и выражений на эквивалентность (поэтому N >= 0 не субсумирует N > 0 )

В частности, сначала P преобразуется в дизъюнктивную нормальную форму, а Q преобразуется в конъюнктивную нормальную форму, после чего они сравниваются следующим образом:

  • каждая атомарная ограничительная конструкция A подразумевает эквивалентную атомарную ограничительную конструкцию A
  • каждая атомарная ограничительная конструкция A подразумевает дизъюнкцию A||B и не подразумевает конъюнкцию A&&B
  • каждая конъюнкция A&&B подразумевает A , но дизъюнкция A||B не подразумевает A

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

Если объявления D1 и D2 являются ограниченными и нормализованные ограничения D1 поглощают нормализованные ограничения D2 (или если D1 ограничено, а D2 не ограничено), то говорят, что D1 как минимум так же ограничено как D2. Если D1 как минимум так же ограничено как D2, а D2 не является как минимум так же ограниченным как D1, то D1 более ограничено чем D2.

template<typename T>
concept bool Decrementable = requires(T t) { --t; };
template<typename T>
concept bool RevIterator = Decrementable<T> && requires(T t) { *t; };
// RevIterator включает Decrementable, но не наоборот
// RevIterator является более ограниченным, чем Decrementable
void f(Decrementable); // #1
void f(RevIterator);   // #2
f(0);       // int удовлетворяет только Decrementable, выбирается #1
f((int*)0); // int* удовлетворяет обоим ограничениям, выбирается #2 как более ограниченный
void g(auto);          // #3 (неограниченный)
void g(Decrementable); // #4
g(true);  // bool не удовлетворяет Decrementable, выбирается #3
g(0);     // int удовлетворяет Decrementable, выбирается #4 как более ограниченный

Ключевые слова

concept , requires

Поддержка компиляторами

GCC >= 6.1 поддерживает эту техническую спецификацию (требуемая опция - fconcepts )