Namespaces
Variants

Constraints and concepts

From cppreference.net


Esta página describe una característica experimental del lenguaje central. Para los requisitos de tipo con nombre utilizados en la especificación de la biblioteca estándar, consulte named requirements

Plantillas de clase , plantillas de función , y funciones no plantilla (típicamente miembros de plantillas de clase) pueden estar asociadas con una restricción , que especifica los requisitos sobre los argumentos de plantilla, los cuales pueden utilizarse para seleccionar las sobrecargas de función y especializaciones de plantilla más apropiadas.

Las restricciones también pueden utilizarse para limitar la deducción automática de tipos en declaraciones de variables y tipos de retorno de funciones únicamente a los tipos que satisfacen los requisitos especificados.

Los conjuntos nombrados de tales requisitos se denominan concepts . Cada concepto es un predicado, evaluado en tiempo de compilación, y se convierte en parte de la interfaz de una plantilla donde se utiliza como restricción:

#include <string>
#include <locale>
using namespace std::literals;
// Declaración del concepto "EqualityComparable", que es satisfecho por
// cualquier tipo T tal que para valores a y b de tipo T,
// la expresión a==b compila y su resultado es convertible a bool
template<typename T>
concept bool EqualityComparable = requires(T a, T b) {
    { a == b } -> bool;
};
void f(EqualityComparable&&); // declaración de una plantilla de función restringida
// template<typename T>
// void f(T&&) requires EqualityComparable<T>; // forma larga equivalente
int main() {
  f("abc"s); // OK, std::string es EqualityComparable
  f(std::use_facet<std::ctype<char>>(std::locale{})); // Error: no es EqualityComparable 
}

Las violaciones de restricciones se detectan en tiempo de compilación, al inicio del proceso de instanciación de plantillas, lo que genera mensajes de error fáciles de seguir.

std::list<int> l = {3,-1,10};
std::sort(l.begin(), l.end()); 
//Diagnóstico típico del compilador sin conceptos:
//  invalid operands to binary expression ('std::_List_iterator<int>' and
//  'std::_List_iterator<int>')
//                           std::__lg(__last - __first) * 2);
//                                     ~~~~~~ ^ ~~~~~~~
// ... 50 líneas de salida ...
//
//Diagnóstico típico del compilador con conceptos:
//  error: cannot call std::sort with std::_List_iterator<int>
//  note:  concept RandomAccessIterator<std::_List_iterator<int>> was not satisfied

El propósito de los concepts es modelar categorías semánticas (Number, Range, RegularFunction) en lugar de restricciones sintácticas (HasPlus, Array). Según la guía principal de ISO C++ T.20 , "La capacidad de especificar una semántica significativa es una característica definitoria de un verdadero concept, a diferencia de una restricción sintáctica."

Si las pruebas de características son compatibles, las características descritas aquí se indican mediante la constante macro __cpp_concepts con un valor igual o mayor a 201507 .

Contenidos

Marcadores de posición

El placeholder sin restricciones auto y los placeholders con restricciones que tienen la forma concept-name < template-argument-list (opcional) > , son placeholders para el tipo que será deducido.

Los placeholders pueden aparecer en declaraciones de variables (en cuyo caso se deducen del inicializador) o en tipos de retorno de funciones (en cuyo caso se deducen de las sentencias return)

std::pair<auto, auto> p2 = std::make_pair(0, 'a'); // primer auto es int,
                                                   // segundo auto es char
Sortable x = f(y); // el tipo de x se deduce del tipo de retorno de f,
                   // solo compila si el tipo satisface la restricción Sortable
auto f(Container) -> Sortable; // el tipo de retorno se deduce de la sentencia return
                               // solo compila si el tipo satisface Sortable

Los placeholders también pueden aparecer en parámetros, en cuyo caso convierten las declaraciones de función en declaraciones de template (con restricciones si el placeholder está restringido)

void f(std::pair<auto, EqualityComparable>); // esta es una plantilla con dos parámetros:
       // parámetro de tipo sin restricciones y parámetro no-tipo restringido

Los placeholders restringidos pueden usarse en cualquier lugar donde auto pueda utilizarse, por ejemplo, en declaraciones de lambdas genéricas

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

Si un especificador de tipo restringido designa un no-tipo o una plantilla, pero se utiliza como un marcador de posición restringido, el programa está mal formado:

template<size_t N> concept bool Even = (N%2 == 0);
struct S1 { int n; };
int Even::* p2 = &S1::n; // error, uso inválido de un concepto no-tipo
void f(std::array<auto, Even>); // error, uso inválido de un concepto no-tipo
template<Even N> void f(std::array<auto, N>); // OK

Plantillas abreviadas

Si uno o más marcadores de posición aparecen en una lista de parámetros de función, la declaración de función es en realidad una declaración de plantilla de función, cuya lista de parámetros de plantilla incluye un parámetro inventado para cada marcador de posición único, en orden de aparición

// forma corta
void g1(const EqualityComparable*, Incrementable&);
// forma larga:
// template<EqualityComparable T, Incrementable U> void g1(const T*, U&);
// forma más larga:
// template<typename T, typename U>
// void g1(const T*, U&) requires EqualityComparable<T> && Incrementable<U>;
void f2(std::vector<auto*>...);
// forma larga: template<typename... T> void f2(std::vector<T*>...);
void f4(auto (auto::*)(auto));
// forma larga: template<typename T, typename U, typename V> void f4(T (U::*)(V));

Todos los placeholders introducidos por especificadores de tipo con restricciones equivalentes tienen el mismo parámetro de plantilla inventado. Sin embargo, cada especificador sin restricciones ( auto ) siempre introduce un parámetro de plantilla diferente

void f0(Comparable a, Comparable* b);
// forma larga: template<Comparable T> void f0(T a, T* b);
void f1(auto a, auto* b);
// forma larga: template<typename T, typename U> f1(T a, U* b);

Tanto las plantillas de función como las plantillas de clase pueden declararse utilizando una introducción de plantilla , que tiene la sintaxis nombre-del-concepto { lista-de-parámetros (opcional) } , en cuyo caso la palabra clave template no es necesaria: cada parámetro de la lista-de-parámetros de la introducción de plantilla se convierte en un parámetro de plantilla cuyo tipo (tipo, no-tipo, plantilla) está determinado por el tipo del parámetro correspondiente en el concepto nombrado.

Además de declarar una plantilla, la introducción de plantilla asocia una restricción de predicado (ver abajo) que nombra (para conceptos de variable) o invoca (para conceptos de función) el concepto nombrado por la introducción.

EqualityComparable{T} class Foo;
// forma larga: template<EqualityComparable T> class Foo;
// forma más larga: template<typename T> requires EqualityComparable<T> class Foo;
template<typename T, int N, typename... Xs> concept bool Example = ...;
Example{A, B, ...C} struct S1;
// forma larga template<class A, int B, class... C> requires Example<A,B,C...> struct S1;

Para plantillas de función, la introducción de plantilla se puede combinar con marcadores de posición:

Sortable{T} void f(T, auto);
// forma larga: template<Sortable T, typename U> void f(T, U);
// alternativa usando solo placeholders: void f(Sortable, auto);

Conceptos

Un concepto es un conjunto nombrado de requisitos. La definición de un concepto aparece en el ámbito del espacio de nombres y tiene la forma de una plantilla de función (en cuyo caso se denomina concepto de función ) o una plantilla de variable (en cuyo caso se denomina concepto de variable ). La única diferencia es que la palabra clave concept aparece en la secuencia de especificadores de declaración :

// concepto de variable de la biblioteca estándar (Ranges TS)
template <class T, class U>
concept bool Derived = std::is_base_of<U, T>::value;
// concepto de función de la biblioteca estándar (Ranges TS)
template <class T>
concept bool EqualityComparable() { 
    return requires(T a, T b) { {a == b} -> Boolean; {a != b} -> Boolean; };
}

Las siguientes restricciones se aplican a los conceptos de función:

  • inline y constexpr no están permitidos, la función es automáticamente inline y constexpr
  • friend y virtual no están permitidos
  • la especificación de excepciones no está permitida, la función es automáticamente noexcept(true) .
  • no puede ser declarada y definida posteriormente, no puede ser redeclarada
  • el tipo de retorno debe ser bool
  • la deducción del tipo de retorno no está permitida
  • la lista de parámetros debe estar vacía
  • el cuerpo de la función debe consistir únicamente en una sentencia return , cuyo argumento debe ser una expresión de restricción (restricción de predicado, conjunción/disyunción de otras restricciones, o una expresión requires, ver más abajo)

Las siguientes restricciones se aplican a los conceptos de variable:

  • Debe tener el tipo bool
  • No puede declararse sin un inicializador
  • No puede declararse en el ámbito de clase
  • constexpr no está permitido, la variable es automáticamente constexpr
  • el inicializador debe ser una expresión de restricción (restricción de predicado, conjunción/disyunción de restricciones, o una expresión requires, ver más abajo)

Los conceptos no pueden referirse recursivamente a sí mismos en el cuerpo de la función o en el inicializador de la variable:

template<typename T>
concept bool F() { return F<typename T::type>(); } // error
template<typename T>
concept bool V = V<T*>; // error

Las instanciaciones explícitas, especializaciones explícitas o especializaciones parciales de conceptos no están permitidas (no se puede cambiar el significado de la definición original de una constraint)

Restricciones

Una restricción es una secuencia de operaciones lógicas que especifica requisitos sobre los argumentos de plantilla. Pueden aparecer dentro de expresiones requires (ver abajo) y directamente como cuerpos de conceptos

Hay 9 tipos de restricciones:

1) conjunciones
2) disyunciones
3) restricciones de predicado
4) restricciones de expresión (solo en una requires-expression )
5) restricciones de tipo (solo en una requires-expression )
6) restricciones de conversión implícita (solo en una requires-expression )
7) restricciones de deducción de argumentos (solo en una expresión requires )
8) restricciones de excepción (solo en una requires-expression )
9) restricciones parametrizadas (solo en una requires-expression )

Los primeros tres tipos de restricciones pueden aparecer directamente como el cuerpo de un concepto o como una cláusula requires ad-hoc:

template<typename T>
requires // cláusula requires (restricción ad-hoc)
sizeof(T) > 1 && get_value<T>() // conjunción de dos restricciones de predicado
void f(T);

Cuando múltiples restricciones están asociadas a la misma declaración, la restricción total es una conjunción en el siguiente orden: la restricción introducida por la introducción de plantilla , restricciones para cada parámetro de plantilla en orden de aparición, la cláusula requires después de la lista de parámetros de plantilla, restricciones para cada parámetro de función en orden de aparición, cláusula final requires :

// las declaraciones declaran la misma plantilla de función restringida 
// con la restricción Incrementable<T> && Decrementable<T>
template<Incrementable T> void f(T) requires Decrementable<T>;
template<typename T> requires Incrementable<T> && Decrementable<T> void f(T); // correcto
// las siguientes dos declaraciones tienen restricciones diferentes:
// la primera declaración tiene Incrementable<T> && Decrementable<T>
// la segunda declaración tiene Decrementable<T> && Incrementable<T>
// Aunque son lógicamente equivalentes.
// La segunda declaración está mal formada, no se requiere diagnóstico
template<Incrementable T> requires Decrementable<T> void g();
template<Decrementable T> requires Incrementable<T> void g(); // error

Conjunciones

Conjunción de restricciones P y Q se especifica como P && Q .

// ejemplo de conceptos de la biblioteca estándar (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>;

Una conjunción de dos restricciones se satisface solo si ambas restricciones se satisfacen. Las conjunciones se evalúan de izquierda a derecha y tienen cortocircuito (si la restricción izquierda no se satisface, la sustitución de argumentos de plantilla en la restricción derecha no se intenta: esto evita fallos debido a sustitución fuera del contexto inmediato). No se permiten sobrecargas definidas por el usuario de operator&& en conjunciones de restricciones.

Disyunciones

Disyunción de restricciones P y Q se especifica como P || Q .

Una disyunción de dos restricciones se satisface si cualquiera de las restricciones se satisface. Las disyunciones se evalúan de izquierda a derecha y tienen cortocircuito (si la restricción izquierda se satisface, la deducción de argumentos de plantilla en la restricción derecha no se intenta). No se permiten sobrecargas definidas por el usuario de operator|| en disyunciones de restricciones.

// ejemplo de restricción de la biblioteca estándar (Ranges TS)
template <class T = void>
requires EqualityComparable<T>() || Same<T, void>
struct equal_to;

Restricciones de predicado

Una restricción de predicado es una expresión constante de tipo bool . Se satisface únicamente si se evalúa como true

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

Las restricciones de predicado pueden especificar requisitos sobre parámetros de plantilla no tipo y sobre argumentos de plantilla de plantilla.

Las restricciones de predicado deben evaluarse directamente a bool , no se permiten conversiones:

template<typename T> struct S {
    constexpr explicit operator bool() const { return true; }
};
template<typename T>
requires S<T>{} // restricción de predicado incorrecta: S<T>{} no es bool
void f(T);
f(0); // error: restricción nunca satisfecha

Requisitos

La palabra clave requires se utiliza de dos maneras:

1) Para introducir una cláusula requires , que especifica restricciones sobre los argumentos de plantilla o sobre una declaración de función.
template<typename T>
void f(T&&) requires Eq<T>; // can appear as the last element of a function declarator
template<typename T> requires Addable<T> // or right after a template parameter list
T add(T a, T b) { return a + b; }
En este caso, la palabra clave requires debe ir seguida de alguna expresión constante (por lo que es posible escribir "requires true;"), pero la intención es que se utilice un concepto nombrado (como en el ejemplo anterior) o una conjunción/disyunción de conceptos nombrados o una requires-expression .
2) Para comenzar una requires-expression , que es una expresión prvalue de tipo bool que describe las restricciones sobre algunos argumentos de plantilla. Tal expresión es true si el concepto correspondiente se satisface, y false en caso contrario:
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; }

La sintaxis de la requires-expression es la siguiente:

requires ( lista-de-parámetros (opcional) ) { secuencia-de-requisitos }
parameter-list - una lista separada por comas de parámetros como en una declaración de función, excepto que no se permiten argumentos predeterminados y el último parámetro no puede ser una elipsis. Estos parámetros no tienen almacenamiento, vinculación ni duración. Estos parámetros están en alcance hasta el cierre } de la requirement-seq . Si no se utilizan parámetros, los paréntesis también pueden omitirse
requirement-seq - secuencia separada por espacios en blanco de requisitos , descritos a continuación (cada requisito termina con un punto y coma). Cada requisito añade otra restricción a la conjunción de restricciones que esta expresión de requisitos define.

Cada requisito en la requirements-seq es uno de los siguientes:

  • requisito simple
  • requisitos de tipo
  • requisitos compuestos
  • requisitos anidados

Los requisitos pueden referirse a los parámetros de plantilla que están en alcance y a los parámetros locales introducidos en la parameter-list . Cuando está parametrizada, una expresión requires se dice que introduce una restricción parametrizada

La sustitución de argumentos de plantilla en una expresión requires puede resultar en la formación de tipos o expresiones inválidos en sus requisitos. En tales casos,

  • Si ocurre un fallo de sustitución en una expresión requires que se utiliza fuera de una entidad con plantilla declaración, entonces el programa está mal formado.
  • Si la expresión requires se utiliza en una declaración de una entidad con plantilla , la restricción correspondiente se trata como "no satisfecha" y el fallo de sustitución no es un error , sin embargo
  • Si ocurriría un fallo de sustitución en una expresión requires para cada posible argumento de plantilla, el programa está mal formado, sin requerir diagnóstico:
template<class T> concept bool C = requires {
    new int[-(int)sizeof(T)]; // inválido para todo T: mal formado, no se requiere diagnóstico
};

Requisitos simples

Un requisito simple es una declaración de expresión arbitraria. El requisito es que la expresión sea válida (esto es una restricción de expresión ). A diferencia de las restricciones de predicado, no se realiza la evaluación, solo se verifica la corrección del lenguaje.

template<typename T>
concept bool Addable =
requires (T a, T b) {
    a + b; // "la expresión a+b es una expresión válida que compilará"
};
// ejemplo de restricción de la biblioteca estándar (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));
};

Requisitos de tipo

Un requisito de tipo es la palabra clave typename seguida de un nombre de tipo, opcionalmente calificado. El requisito es que el tipo nombrado exista (una restricción de tipo ): esto puede usarse para verificar que existe cierto tipo anidado con nombre, o que una especialización de plantilla de clase denomina un tipo, o que una plantilla de alias denomina un tipo.

template<typename T> using Ref = T&;
template<typename T> concept bool C =
requires {
    typename T::inner; // nombre de miembro anidado requerido
    typename S<T>;     // especialización de plantilla de clase requerida
    typename Ref<T>;   // sustitución de plantilla de alias requerida
};
// Ejemplo de concepto de la biblioteca estándar (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> es válido y nombra un tipo
    { CommonType<T, U>{std::forward<T>(t)} }; 
    { CommonType<T, U>{std::forward<U>(u)} }; 
};

Requisitos Compuestos

Un requisito compuesto tiene la forma

{ expresión } noexcept (opcional) tipo-de-retorno-final (opcional) ;

y especifica una conjunción de las siguientes restricciones:

1) expression es una expresión válida ( expresión de restricción )
2) Si se utiliza noexcept , la expresión también debe ser noexcept ( restricción de excepción )
3) Si el trailing-return-type nombra un tipo que utiliza placeholders, el tipo debe ser deducible del tipo de la expresión ( restricción de deducción de argumentos )
4) Si el trailing-return-type nombra un tipo que no utiliza placeholders, entonces se añaden dos restricciones más:
4a) el tipo nombrado por trailing-return-type es válido ( restricción de tipo )
4b) el resultado de la expresión es implícitamente convertible a ese tipo ( restricción de conversión implícita )
template<typename T> concept bool C2 =
requires(T x) {
    {*x} -> typename T::inner; // la expresión *x debe ser válida
                               // Y el tipo T::inner debe ser válido
                               // Y el resultado de *x debe ser convertible a T::inner
};
// Ejemplo de concepto de la biblioteca estándar (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) }; // la restricción de inicialización directa debe usar expresión
    { !b1 } -> bool; // restricción compuesta
    requires Same<decltype(b1 && b2), bool>; // restricción anidada, ver abajo
    requires Same<decltype(b1 || b2), bool>;
};

Requisitos anidados

Un requisito anidado es otra cláusula requires , terminada con un punto y coma. Esto se utiliza para introducir restricciones de predicado (ver arriba) expresadas en términos de otros conceptos nombrados aplicados a los parámetros locales (fuera de una cláusula requires, las restricciones de predicado no pueden usar parámetros, y colocar una expresión directamente en una cláusula requires la convierte en una restricción de expresión lo que significa que no se evalúa)

// ejemplo de restricción del 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)>;  // anidada: "Same<...> se evalúa como verdadero"
    { a.~T() } noexcept;  // compuesta: "a.~T()" es una expresión válida que no lanza excepciones
    requires Same<T*, decltype(new T)>; // anidada: "Same<...> se evalúa como verdadero"
    requires Same<T*, decltype(new T[n])>; // anidada
    { delete new T };  // compuesta
    { delete new T[n] }; // compuesta
};

Resolución de conceptos

Al igual que cualquier otra plantilla de función, un concepto de función (pero no un concepto de variable) puede sobrecargarse: pueden proporcionarse múltiples definiciones de concepto que utilicen el mismo concept-name .

La resolución de conceptos se realiza cuando un concept-name (que puede ser calificado) aparece en

1) un especificador de tipo restringido void f ( Concept ) ; std:: vector < Concept > x = ... ;
2) un parámetro restringido template < Concept T > void f ( ) ;
3) una introducción a plantillas Concept { T } struct X ;
4) una expresión de restricción 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); // el conjunto de conceptos referenciados por C incluye tanto #1 como #2;
           // la resolución de conceptos (ver abajo) selecciona #1.

Para realizar la resolución de conceptos, template parameters de cada concepto que coincide con el nombre (y la calificación, si la hay) se empareja con una secuencia de concept arguments , que son argumentos de plantilla y wildcards . Un wildcard puede coincidir con un parámetro de plantilla de cualquier tipo (tipo, no-tipo, plantilla). El conjunto de argumentos se construye de manera diferente, dependiendo del contexto

1) Para un nombre de concepto utilizado como parte de un especificador de tipo restringido o parámetro, si el nombre del concepto se utiliza sin una lista de parámetros, la lista de argumentos es un único comodín.
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) Para un nombre de concepto utilizado como parte de un especificador de tipo restringido o parámetro, si el nombre del concepto se utiliza con una lista de argumentos de plantilla, la lista de argumentos es un comodín único seguido de esa lista de argumentos.
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) Si un concepto aparece en una introducción de plantilla, la lista de argumentos es una secuencia de marcadores de posición tan larga como la lista de parámetros en la introducción de plantilla
template<typename... Ts>
concept bool C3 = true;
C3{T} void q2();     // OK: <T> coincide con <...Ts>
C3{...Ts} void q1(); // OK: <...Ts> coincide con <...Ts>
4) Si un concepto aparece como el nombre de un template-id, la lista de argumentos del concepto es exactamente la secuencia de argumentos de ese template-id
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

La resolución de conceptos se realiza emparejando cada argumento con el parámetro correspondiente de cada concepto visible. Los argumentos de plantilla predeterminados (si se utilizan) se instancian para cada parámetro que no corresponde a un argumento, y luego se añaden a la lista de argumentos. Un parámetro de plantilla coincide con un argumento solo si tiene el mismo tipo (tipo, no-tipo, plantilla), a menos que el argumento sea un comodín. Un paquete de parámetros coincide con cero o más argumentos siempre que todos los argumentos coincidan con el patrón en tipo (a menos que sean comodines).

Si algún argumento no coincide con su parámetro correspondiente o si hay más argumentos que parámetros y el último parámetro no es un pack, el concepto no es viable. Si hay cero o más de un concepto viable, el programa está mal formado.

template<typename T> concept bool C2() { return true; }
template<int T> concept bool C2() { return true; }
template<C2<0> T> struct S1; // error: <comodín, 0> no coincide con 
                             // ni <typename T> ni <int T>
template<C2 T> struct S2; // tanto #1 como #2 coinciden: error

Ordenamiento parcial de restricciones

Antes de cualquier análisis adicional, las restricciones son normalizadas sustituyendo el cuerpo de cada concepto de nombre y cada expresión requires hasta que lo que queda es una secuencia de conjunciones y disyunciones sobre restricciones atómicas, que son restricciones de predicado, restricciones de expresión, restricciones de tipo, restricciones de conversión implícita, restricciones de deducción de argumentos y restricciones de excepción.

Se dice que el concepto P subsume al concepto Q si se puede demostrar que P implica Q sin analizar tipos y expresiones para equivalencia (por lo que N >= 0 no subsume a N > 0 )

Específicamente, primero P se convierte a forma normal disyuntiva y Q se convierte a forma normal conjuntiva, y se comparan de la siguiente manera:

  • cada restricción atómica A subsume a la restricción atómica equivalente A
  • cada restricción atómica A subsume a una disyunción A||B y no subsume a una conjunción A&&B
  • cada conjunción A&&B subsume a A , pero una disyunción A||B no subsume a A

La relación de subsunción define un orden parcial de restricciones, que se utiliza para determinar:

Si las declaraciones D1 y D2 están restringidas y las restricciones normalizadas de D1 subsumen las restricciones normalizadas de D2 (o si D1 está restringida y D2 no está restringida), entonces se dice que D1 es al menos tan restringida como D2. Si D1 es al menos tan restringida como D2 y D2 no es al menos tan restringida como D1, entonces D1 es más restringida que D2.

template<typename T>
concept bool Decrementable = requires(T t) { --t; };
template<typename T>
concept bool RevIterator = Decrementable<T> && requires(T t) { *t; };
// RevIterator subsume a Decrementable, pero no al revés
// RevIterator está más restringido que Decrementable
void f(Decrementable); // #1
void f(RevIterator);   // #2
f(0);       // int solo satisface Decrementable, selecciona #1
f((int*)0); // int* satisface ambas restricciones, selecciona #2 por estar más restringido
void g(auto);          // #3 (sin restricciones)
void g(Decrementable); // #4
g(true);  // bool no satisface Decrementable, selecciona #3
g(0);     // int satisface Decrementable, selecciona #4 porque está más restringido

Palabras clave

concept , requires

Soporte del compilador

GCC >= 6.1 admite esta especificación técnica (opción requerida - fconcepts )