Um dos cantos dos conceitos do C ++ 20 é que existem certas situações nas quais você precisa escrever requires requires
. Por exemplo, este exemplo de [expr.prim.req] / 3 :
Uma expressão de requisitos também pode ser usada em uma cláusula de requisitos ([temp]) como uma maneira de escrever restrições ad hoc nos argumentos do modelo, como o abaixo:
template<typename T> requires requires (T x) { x + x; } T add(T a, T b) { return a + b; }
O primeiro requer introduz a cláusula requirida e o segundo introduz a expressão requer .
Qual é a razão técnica por trás da necessidade dessa segunda requires
palavra-chave? Por que não podemos simplesmente permitir a escrita:
template<typename T>
requires (T x) { x + x; }
T add(T a, T b) { return a + b; }
(Nota: por favor, não responda que a gramática requires
)
c++
c++-concepts
c++20
Barry
fonte
fonte
noexcept(noexcept(...))
.requires
são homônimos na minha opinião: eles têm a mesma aparência, soletram o mesmo, têm o mesmo cheiro, mas são intrinsecamente diferentes. Se eu sugerir uma correção, sugiro renomear uma delas.co_requires
? (Desculpe, não pude resistir).long long
.Respostas:
É porque a gramática exige. Faz.
Uma
requires
restrição não precisa usar umarequires
expressão. Ele pode usar qualquer expressão constante booleana mais ou menos arbitrária. Portanto,requires (foo)
deve ser um legítimorequires
restrição .Uma
requires
expressão (aquilo que testa se certas coisas seguem certas restrições) é uma construção distinta; é apenas introduzido pela mesma palavra-chave.requires (foo f)
seria o começo de umarequires
expressão válida .O que você deseja é que, se você usar
requires
um local que aceite restrições, poderá criar uma "restrição + expressão" darequires
cláusula.Então, aqui está a pergunta: se você colocar
requires (foo)
em um local apropriado para a requer restrição ... até que ponto o analisador deve ir antes de perceber que essa é uma restrição requer e não uma restrição + expressão da maneira que você deseja ser estar?Considere isto:
Se
foo
é um tipo,(foo)
é uma lista de parâmetros de uma expressão requerida, e tudo no{}
não é o corpo da função, mas o corpo dessarequires
expressão. Caso contrário,foo
é uma expressão em umarequires
cláusula.Bem, você poderia dizer que o compilador deve apenas descobrir o que
foo
é o primeiro. Mas o C ++ realmente não gosta quando o ato básico de analisar uma sequência de tokens exige que o compilador descubra o que esses identificadores significam antes que possa entender os tokens. Sim, o C ++ é sensível ao contexto, então isso acontece. Mas o comitê prefere evitá-lo sempre que possível.Então, sim, é gramática.
fonte
requires
aparecer após um<>
conjunto de argumentos de modelo ou após uma lista de parâmetros de função, será uma cláusula required. Serequires
aparecer onde uma expressão é válida, é uma expressão requer. Isso pode ser determinado pela estrutura da árvore de análise, não pelo conteúdo da árvore de análise (as especificidades de como um identificador é definido seriam o conteúdo da árvore).concept
erequires
. A introdução de um terceiro, quando o segundo seria capaz de cobrir ambos os casos, sem problemas gramaticais e poucos problemas voltados para o usuário, é apenas um desperdício. Afinal, o único problema visual é que a palavra-chave é repetida duas vezes.A situação é exatamente análoga a
noexcept(noexcept(...))
. Claro, isso parece mais uma coisa ruim do que uma coisa boa, mas deixe-me explicar. :) Vamos começar com o que você já sabe:O C ++ 11 tem "
noexcept
-clauses" e "-expressions"noexcept
. Eles fazem coisas diferentes.A
noexcept
cláusula diz: "Esta função deve ser exceto quando ... (alguma condição)." Ele segue uma declaração de função, pega um parâmetro booleano e causa uma alteração comportamental na função declarada.Uma
noexcept
expressão diz: "Compilador, por favor me diga se (alguma expressão) é nenhuma exceção". É ela própria uma expressão booleana. Não tem "efeitos colaterais" no comportamento do programa - está apenas pedindo ao compilador a resposta para uma pergunta de sim / não. "Esta expressão é exceto?"Nós podemos um ninho
noexcept
-expression dentro de umnoexcept
-clause, mas nós normalmente considerá-lo ruim estilo para fazê-lo.É considerado um estilo melhor encapsular a
noexcept
expressão em uma característica de tipo.O rascunho de trabalho do C ++ 2a possui "
requires
-clauses" e "-expressions"requires
. Eles fazem coisas diferentes.A
requires
cláusula diz: "Esta função deve participar da resolução de sobrecarga quando ... (alguma condição)." Ele segue uma declaração de função, pega um parâmetro booleano e causa uma alteração comportamental na função declarada.Uma
requires
expressão diz: "Compilador, diga-me se (algum conjunto de expressões) está bem formado." É ela própria uma expressão booleana. Não tem "efeitos colaterais" no comportamento do programa - é apenas pedir ao compilador a resposta para uma pergunta de sim / não. "Esta expressão está bem formada?"Nós podemos um ninho
requires
-expression dentro de umrequires
-clause, mas nós normalmente considerá-lo ruim estilo para fazê-lo.É considerado um estilo melhor encapsular a
requires
expressão em uma característica de tipo ...... ou em um conceito (C ++ 2a Working Draft).
fonte
noexcept
tem o problema quenoexcept(f())
poderia significar quer interpretarf()
como um boolean que estamos usando para definir a especificação ou verificar se há ou nãof()
énoexcept
.requires
não tem essa ambiguidade porque as expressões que sua verificação de validade já precisam ser introduzidas com{}
s. Depois disso, o argumento é basicamente "a gramática diz isso".{}
são opcionais.{}
são opcionais, não é isso que esse comentário mostra. No entanto, esse é um ótimo comentário que demonstra a ambiguidade da análise. Provavelmente aceitar que o comentário (com alguma explicação) como uma resposta autônomorequires is_nothrow_incrable_v<T>;
deve serrequires is_incrable_v<T>;
Acho que a página de conceitos da cppreference explica isso. Eu posso explicar com "matemática", por assim dizer, por que isso deve ser assim:
Se você deseja definir um conceito, faça o seguinte:
Se você deseja declarar uma função que usa esse conceito, faça o seguinte:
Agora, se você não deseja definir o conceito separadamente, acho que tudo o que você precisa fazer é substituir. Pegue esta peça
requires (T x) { x + x; };
e substitua aAddable<T>
peça, e você obterá:é sobre isso que você está perguntando.
fonte
template<typename T> requires (T x) { x + x; }
e exigir que exigir pode ser tanto a cláusula quanto a expressão?requires
cláusula -as-restringir não precisa ser umarequires
expressão. Esse é apenas um possível uso disso.requires requires
. Eles poderiam ter acrescentado algo à gramática para permitir,template<typename T> requires (T x) { x + x; }
mas não o fizeram. Barry quer saber por que eles não fizeramtemplate<class T> void f(T) requires requires(T (x)) { (void)x; };
significa algo diferente se você remover um dosrequires
es.Achei um comentário de Andrew Sutton (um dos autores do Concepts, que o implementou no gcc) ser bastante útil nesse sentido, então pensei em citá-lo aqui na íntegra:
fonte
Porque você está dizendo que uma coisa A tem um requisito B e o requisito B tem um requisito C.
A coisa A requer B, que por sua vez requer C.
A cláusula "required" requer algo.
Você tem a coisa A (exigindo B (exigindo C)).
Meh. :)
fonte
requires
não são conceitualmente a mesma coisa (uma é uma cláusula, outra uma expressão). De fato, se eu entendi corretamente, os dois conjuntos de()
inrequires (requires (T x) { x + x; })
têm significados muito diferentes (o externo é opcional e sempre contém um cons booleano; o interno é uma parte obrigatória da introdução de uma expressão requer e não permite expressões reais).