Una de las esquinas de los conceptos de C ++ 20 es que hay ciertas situaciones en las que tienes que escribir requires requires
. Por ejemplo, este ejemplo de [expr.prim.req] / 3 :
Una expresión require también se puede usar en una cláusula require ([temp]) como una forma de escribir restricciones ad hoc en argumentos de plantilla como el siguiente:
template<typename T> requires requires (T x) { x + x; } T add(T a, T b) { return a + b; }
El primero requiere introduce la cláusula require , y el segundo introduce la expresión require .
¿Cuál es la razón técnica detrás de necesitar esa segunda requires
palabra clave? ¿Por qué no podemos simplemente permitir escribir:
template<typename T>
requires (T x) { x + x; }
T add(T a, T b) { return a + b; }
(Nota: por favor no responda que la gramática requires
es)
c++
c++-concepts
c++20
Barry
fuente
fuente
noexcept(noexcept(...))
.requires
son homónimos en mi opinión: se ven iguales, deletrean igual, huelen igual, pero son intrínsecamente diferentes. Si tuviera que sugerir una solución, sugeriría cambiar el nombre de uno de ellos.co_requires
? (Lo siento, no pude resistir).long long
.Respuestas:
Es porque la gramática lo requiere. Lo hace.
Una
requires
restricción no tiene que usar unarequires
expresión. Puede usar cualquier expresión constante booleana más o menos arbitraria. Por lo tanto,requires (foo)
debe ser unarequires
restricción legítima .Una
requires
expresión (esa cosa que prueba si ciertas cosas siguen ciertas restricciones) es una construcción distinta; solo se introduce con la misma palabra clave.requires (foo f)
Sería el comienzo de unarequires
expresión válida .Lo que quiere es que si lo usa
requires
en un lugar que acepta restricciones, debería poder hacer una "restricción + expresión" fuera de larequires
cláusula.Así que aquí está la pregunta: si coloca
requires (foo)
en un lugar que sea apropiado para una restricción requerida ... ¿qué tan lejos tiene que ir el analizador antes de que pueda darse cuenta de que esta es una restricción requerida en lugar de una restricción + expresión de la manera que lo desea? ¿ser - estar?Considera esto:
Si
foo
es un tipo, entonces(foo)
es una lista de parámetros de una expresión requerida, y todo en el{}
no es el cuerpo de la función sino el cuerpo de esarequires
expresión. De lo contrario,foo
es una expresión en unarequires
cláusula.Bueno, se podría decir que el compilador debería descubrir qué
foo
es primero. Pero a C ++ realmente no le gusta cuando el acto básico de analizar una secuencia de tokens requiere que el compilador descubra qué significan esos identificadores antes de que pueda dar sentido a los tokens. Sí, C ++ es sensible al contexto, por lo que esto sucede. Pero el comité prefiere evitarlo cuando sea posible.Entonces sí, es gramática.
fuente
requires
aparece después de un<>
conjunto de argumentos de plantilla o después de una lista de parámetros de función, entonces es una cláusula obligatoria. Sirequires
aparece donde una expresión es válida, entonces es una expresión obligatoria. Esto puede determinarse por la estructura del árbol de análisis, no por el contenido del árbol de análisis (los detalles de cómo se define un identificador serían los contenidos del árbol).concept
yrequires
. Introducir un tercero, cuando el segundo sería capaz de cubrir ambos casos sin problemas gramaticales y con pocos problemas para el usuario, es un desperdicio. Después de todo, el único problema visual es que la palabra clave se repite dos veces.La situación es exactamente análoga a
noexcept(noexcept(...))
. Claro, esto suena más como algo malo que como algo bueno, pero déjame explicarte. :) Comenzaremos con lo que ya sabes:C ++ 11 tiene "
noexcept
-cláusulas" y "noexcept
-expresiones". Ellos hacen cosas diferentes.Una
noexcept
cláusula dice: "Esta función no debe ser excepto cuando ... (alguna condición)". Va en una declaración de función, toma un parámetro booleano y provoca un cambio de comportamiento en la función declarada.Una expresión
noexcept
-expresa, "Compilador, por favor dime si (alguna expresión) no es excepto". Es en sí misma una expresión booleana. No tiene "efectos secundarios" en el comportamiento del programa, solo está pidiendo al compilador la respuesta a una pregunta de sí / no. "¿Esta expresión no es la excepción?"Nos puede anidar un
noexcept
-expresión dentro de unnoexcept
-clause, pero por lo general consideramos que es un mal estilo para hacerlo.Se considera un mejor estilo para encapsular la
noexcept
expresión en un rasgo de tipo.El borrador de trabajo de C ++ 2a tiene "
requires
cláusulas" y "expresiones"requires
. Ellos hacen cosas diferentes.Una
requires
cláusula dice: "Esta función debe participar en la resolución de sobrecarga cuando ... (alguna condición)". Va en una declaración de función, toma un parámetro booleano y provoca un cambio de comportamiento en la función declarada.Una expresión-
requires
dice: "Compilador, por favor dígame si (algún conjunto de expresiones) está bien formado". Es en sí misma una expresión booleana. No tiene "efectos secundarios" en el comportamiento del programa, solo está pidiendo al compilador la respuesta a una pregunta de sí / no. "¿Esta expresión está bien formada?"Nos puede anidar un
requires
-expresión dentro de unrequires
-clause, pero por lo general consideramos que es un mal estilo para hacerlo.Se considera mejor estilo encapsular la
requires
expresión en un rasgo de tipo ...... o en un concepto (C ++ 2a Working Draft).
fuente
noexcept
tiene el problema de quenoexcept(f())
podría significar o bien interpretarf()
como un valor lógico que estamos usando para establecer la especificación o comprobar si es o nof()
esnoexcept
.requires
no tiene esta ambigüedad porque las expresiones que verifican su validez ya tienen que ser introducidas con{}
s. Después de eso, el argumento es básicamente "la gramática lo dice".{}
son opcionales.{}
no son opcionales, eso no es lo que muestra ese comentario. Sin embargo, ese es un gran comentario que demuestra la ambigüedad del análisis. Probablemente aceptaría ese comentario (con alguna explicación) como una respuesta independienterequires is_nothrow_incrable_v<T>;
debería serrequires is_incrable_v<T>;
Creo que la página de conceptos de cppreference explica esto. Puedo explicar con "matemáticas", por decir, por qué esto debe ser así:
Si desea definir un concepto, haga esto:
Si desea declarar una función que usa ese concepto, haga esto:
Ahora, si no quieres definir el concepto por separado, supongo que todo lo que tienes que hacer es alguna sustitución. Tome esta parte
requires (T x) { x + x; };
y reemplácelaAddable<T>
, y obtendrá:que es lo que preguntas.
fuente
template<typename T> requires (T x) { x + x; }
y exigir que require pueda ser tanto la cláusula como la expresión?requires
cláusula -as-restricción no tiene que ser unarequires
-expresión. Eso es simplemente un posible uso de él.requires requires
. Podrían haber agregado algo a la gramática para permitir,template<typename T> requires (T x) { x + x; }
pero no lo hicieron. Barry quiere saber por qué no lo hicierontemplate<class T> void f(T) requires requires(T (x)) { (void)x; };
significa algo diferente si elimina uno de losrequires
es.He encontrado un comentario de Andrew Sutton (uno de los autores conceptos, que se implementó en gcc) a ser muy útil en este sentido, así que pensé que acababa de citar aquí en su casi totalidad:
fuente
Porque usted está diciendo que una cosa A tiene un requisito B, y el requisito B tiene un requisito C.
La cosa A requiere B, que a su vez requiere C.
La cláusula "requiere" en sí misma requiere algo.
Tienes la cosa A (que requiere B (que requiere C)).
Meh :)
fuente
requires
no son conceptualmente lo mismo (uno es una cláusula, otro una expresión). De hecho, si entiendo correctamente, los dos conjuntos de()
inrequires (requires (T x) { x + x; })
tienen significados muy diferentes (el exterior es opcional y siempre contiene un constexpr booleano; el interior es una parte obligatoria de la introducción de una expresión obligatoria y no permite expresiones reales).