Perché abbiamo bisogno richiede richiede?


161

Uno degli angoli dei concetti di C ++ 20 è che ci sono alcune situazioni in cui devi scrivere requires requires. Ad esempio, questo esempio tratto da [expr.prim.req] / 3 :

A richiede-espressione può essere utilizzato anche in una richiede clausola ([Temp]) come un modo di scrivere vincoli ad hoc su argomenti del template come quello qui sotto:

template<typename T>
  requires requires (T x) { x + x; }
    T add(T a, T b) { return a + b; }

Il primo richiede introduce la clausola obbligatoria e il secondo introduce l' espressione obbligatoria .

Qual è il motivo tecnico alla base della necessità di quella seconda requiresparola chiave? Perché non possiamo semplicemente consentire la scrittura:

template<typename T>
  requires (T x) { x + x; }
    T add(T a, T b) { return a + b; }

(Nota: per favore non rispondere alla grammatica requires)


25
Suggerimento: "C'è qualcosa che richiede richiede richiede?". Più seriamente, ho la sensazione che sia lo stesso motivo dietro noexcept(noexcept(...)).
Quentin,

11
I due requiressono omonimi secondo me: hanno lo stesso aspetto, lo stesso incantesimo, lo stesso odore, ma sono intrinsecamente diversi. Se dovessi suggerire una soluzione, suggerirei di rinominarne uno.
YSC,

5
@YSC - co_requires? (Mi dispiace, non ho potuto resistere).
StoryTeller - Unslander Monica

122
Dove si fermerà la follia? La prossima cosa che sai, avremo long long.
Eljay,

8
@StoryTeller: "Richiede richiede richiesto?" sarebbe stato ancora più allettativo !!
PW

Risposte:


81

È perché la grammatica lo richiede. Lo fa.

Un requiresvincolo non deve usare requiresun'espressione. Può usare qualsiasi espressione costante booleana più o meno arbitraria. Pertanto, requires (foo)deve essere un requiresvincolo legittimo .

requires Un'espressione (che cosa che verifica se certe cose seguono determinati vincoli) è un costrutto distinto; è appena stato introdotto dalla stessa parola chiave. requires (foo f)sarebbe l'inizio di requiresun'espressione valida .

Quello che vuoi è che se lo usi requiresin un luogo che accetta vincoli, dovresti essere in grado di creare un "vincolo + espressione" fuori dalla requiresclausola.

Quindi, ecco la domanda: se ti metti requires (foo)in un posto che è appropriato per un vincolo obbligatorio ... quanto deve andare lontano il parser prima di rendersi conto che questo è un vincolo obbligatorio piuttosto che un vincolo + espressione nel modo desiderato essere?

Considera questo:

void bar() requires (foo)
{
  //stuff
}

Se fooè un tipo, allora (foo)è un elenco di parametri di un'espressione richiesta e tutto ciò che {}è in non è il corpo della funzione ma il corpo di quell'espressione requires. Altrimenti, fooè un'espressione in una requiresclausola.

Bene, potresti dire che il compilatore dovrebbe solo capire cosa fooè il primo. Ma al C ++ non piace davvero quando l'atto di base dell'analisi di una sequenza di token richiede che il compilatore capisca cosa significano quegli identificatori prima che possa dare un senso ai token. Sì, C ++ è sensibile al contesto, quindi questo accade. Ma il comitato preferisce evitarlo ove possibile.

Quindi sì, è grammatica.


2
Ha senso avere un elenco di parametri con un tipo ma senza un nome?
NathanOliver,

3
@Quentin: Esistono certamente casi di dipendenza dal contesto nella grammatica C ++. Ma il comitato cerca davvero di minimizzarlo, e sicuramente non gli piace aggiungere altro .
Nicol Bolas,

3
@RobertAndrzejuk: se requiresappare dopo una <>serie di argomenti template o dopo un elenco di parametri di funzione, allora è una clausola obbligatoria. Se requiresappare dove un'espressione è valida, allora è un'espressione obbligatoria. Ciò può essere determinato dalla struttura dell'albero di analisi, non dal contenuto dell'albero di analisi (le specifiche di come viene definito un identificatore sarebbero i contenuti dell'albero).
Nicol Bolas,

6
@RobertAndrzejuk: Certo, il requisito-espressione avrebbe potuto usare una parola chiave diversa. Ma le parole chiave hanno costi enormi in C ++, in quanto hanno il potenziale per interrompere qualsiasi programma che ha utilizzato l'identificatore che è diventato una parola chiave. La proposta di concetti ha già introdotto due parole chiave: concepte requires. Introdurre un terzo, quando il secondo sarebbe in grado di coprire entrambi i casi senza problemi grammaticali e con pochi problemi rivolti all'utente, è solo uno spreco. Dopotutto, l'unico problema visivo è che la parola chiave sembra essere ripetuta due volte.
Nicol Bolas,

3
@RobertAndrzejuk è comunque una cattiva pratica incorporare vincoli del genere poiché non si ottiene la sussunzione come se si fosse scritto un concetto. Quindi non è una buona idea prendere un identificatore per una funzione sconsigliata per un uso così basso.
Rakete1111,

60

La situazione è esattamente analoga a noexcept(noexcept(...)). Certo, questo suona più come una cosa brutta che una buona, ma lasciami spiegare. :) Inizieremo con ciò che già sai:

C ++ 11 ha " noexcept-clauses" e " noexcept-expressions". Fanno cose diverse.

  • Un noexcept-clause dice: "Questa funzione non dovrebbe essere esclusa quando ... (alcune condizioni)." Va su una dichiarazione di funzione, accetta un parametro booleano e provoca una modifica comportamentale nella funzione dichiarata.

  • Un noexcept-expression dice: "Compiler, la prego di dirmi se (un po 'di espressione) è noexcept." È di per sé un'espressione booleana. Non ha "effetti collaterali" sul comportamento del programma - sta solo chiedendo al compilatore la risposta a una domanda sì / no. "Questa espressione è assolutamente esclusa?"

Ci può nidificare un noexcept-expression all'interno di un noexcept-clause, ma noi di solito consideriamo cattivo stile di farlo.

template<class T>
void incr(T t) noexcept(noexcept(++t));  // NOT SO HOT

È considerato uno stile migliore per incapsulare l' noexceptespressione in un tratto di tipo.

template<class T> inline constexpr bool is_nothrow_incrable_v =
    noexcept(++std::declval<T&>());  // BETTER, PART 1

template<class T>
void incr(T t) noexcept(is_nothrow_incrable_v<T>);  // BETTER, PART 2

La bozza di lavoro C ++ 2a ha " requires-clauses" e " requires-expressions". Fanno cose diverse.

  • Un requires-clause dice "Questa funzione dovrebbe partecipare alla risoluzione del sovraccarico quando ... (alcune condizioni)." Va su una dichiarazione di funzione, accetta un parametro booleano e provoca una modifica comportamentale nella funzione dichiarata.

  • Un requires-expression dice: "Compiler, la prego di dirmi se (un insieme di espressioni) è ben formato." È di per sé un'espressione booleana. Non ha "effetti collaterali" sul comportamento del programma - sta solo chiedendo al compilatore la risposta a una domanda sì / no. "Questa espressione è ben formata?"

Ci può nidificare un requires-expression all'interno di un requires-clause, ma noi di solito consideriamo cattivo stile di farlo.

template<class T>
void incr(T t) requires (requires(T t) { ++t; });  // NOT SO HOT

È considerato uno stile migliore per incapsulare l' requiresespressione in un tratto di tipo ...

template<class T> inline constexpr bool is_incrable_v =
    requires(T t) { ++t; };  // BETTER, PART 1

template<class T>
void incr(T t) requires is_incrable_v<T>;  // BETTER, PART 2

... o in un concetto (C ++ 2a Working Draft).

template<class T> concept Incrable =
    requires(T t) { ++t; };  // BETTER, PART 1

template<class T>
void incr(T t) requires Incrable<T>;  // BETTER, PART 2

1
Non compro davvero questo argomento. noexceptha il problema che noexcept(f())potrebbe significare sia interpretare f()come un valore booleano che stiamo usando per impostare la specifica o verificare se f()è noexcept. requiresnon ha questa ambiguità perché le espressioni che sta verificando la validità devono già essere introdotte con {}s. Dopodiché, l'argomento è sostanzialmente "la grammatica lo dice".
Barry,

@ Barry: vedi questo commento . Sembra che {}siano opzionali.
Eric

1
@Eric Non {}sono opzionali, non è quello che mostra quel commento. Tuttavia, questo è un grande commento che dimostra l'analisi dell'ambiguità. Accetterebbe probabilmente quel commento (con qualche spiegazione) come risposta autonoma
Barry,

1
requires is_nothrow_incrable_v<T>;dovrebbe essererequires is_incrable_v<T>;
Ruslan il

16

Penso che la pagina dei concetti di cppreference spieghi questo. Posso spiegare con "matematica", per così dire, perché questo deve essere così:

Se vuoi definire un concetto, fai questo:

template<typename T>
concept Addable = requires (T x) { x + x; }; // requires-expression

Se si desidera dichiarare una funzione che utilizza tale concetto, procedere come segue:

template<typename T> requires Addable<T> // requires-clause, not requires-expression
T add(T a, T b) { return a + b; }

Ora, se non vuoi definire il concetto separatamente, suppongo che tutto ciò che devi fare è un po 'di sostituzione. Prendi questa parte requires (T x) { x + x; };e sostituisci la Addable<T>parte e otterrai:

template<typename T> requires requires (T x) { x + x; }
T add(T a, T b) { return a + b; }

che è quello che stai chiedendo.


4
Non credo sia la domanda da porre. Questo spiega la grammatica, più o meno.
Passante Entro il

Ma perché non puoi avere template<typename T> requires (T x) { x + x; }e richiedere quel requisito può essere sia la clausola che l'espressione?
NathanOliver,

2
@NathanOliver: Perché stai costringendo il compilatore a interpretare un costrutto come un altro. Una requiresclausola -as-restring non deve essere requiresun'espressione -ex. Questo è solo un possibile uso di esso.
Nicol Bolas,

2
@TheQuantumPhysicist Ciò a cui stavo arrivando con il mio commento è che questa risposta spiega solo la sintassi. Non quale reale ragione tecnica dobbiamo requires requires. Avrebbero potuto aggiungere qualcosa alla grammatica per consentire, template<typename T> requires (T x) { x + x; }ma non lo fecero. Barry vuole sapere perché non lo hanno fatto
NathanOliver,

3
Se stiamo davvero giocando a trovare l'ambiguità della grammatica qui, OK, ti morderò. godbolt.org/z/i6n8kM template<class T> void f(T) requires requires(T (x)) { (void)x; }; significa qualcosa di diverso se si rimuove una delle requireses.
Quuxplusone,

12

Ho trovato un commento di Andrew Sutton (uno degli autori di Concepts, che lo ha implementato in gcc) per essere abbastanza utile in questo senso, quindi ho pensato di citarlo qui nella sua totalità:

Non molto tempo fa richiede espressioni (la frase introdotta dalla seconda richiede) non era consentita nelle espressioni di vincolo (la frase introdotta dalla prima richiede). Potrebbe apparire solo nelle definizioni dei concetti. In realtà, questo è esattamente ciò che viene proposto nella sezione di quel documento in cui appare tale affermazione.

Tuttavia, nel 2016, c'era una proposta per allentare tale restrizione [Nota del redattore: P0266 ]. Nota la barratura del paragrafo 4 nella sezione 4 del documento. E così è nato richiede richiede.

A dire il vero, non avevo mai implementato quella restrizione in GCC, quindi era sempre stato possibile. Penso che Walter possa averlo scoperto e trovato utile, portando a quel documento.

Per evitare che qualcuno pensasse che non fossi sensibile alla scrittura richiede due volte, ho passato un po 'di tempo a cercare di determinare se ciò potesse essere semplificato. Risposta breve: no.

Il problema è che ci sono due costrutti grammaticali che devono essere introdotti dopo un elenco di parametri del modello: molto comunemente un'espressione di vincolo (come P && Q) e occasionalmente requisiti sintattici (come requires (T a) { ... }). Questa si chiama espressione richiesta.

Il primo richiede introduce il vincolo. Il secondo richiede introduce l'espressione obbligatoria. Questo è solo il modo in cui la grammatica si compone. Non lo trovo affatto confuso.

Ho provato, ad un certo punto, a comprimerli in un unico requisito. Sfortunatamente, ciò porta ad alcuni problemi di analisi seriamente difficili. Non si può facilmente dire, ad esempio se un (after richiede richiede una sottoespressione nidificata o un elenco di parametri. Non credo che ci sia una perfetta disambiguazione di quelle sintassi (vedere la logica per una sintassi di inizializzazione uniforme; anche questo problema è presente).

Quindi fai una scelta: make richiede di introdurre un'espressione (come fa ora) o di introdurre un elenco parametrico di requisiti.

Ho scelto l'approccio attuale perché la maggior parte delle volte (come in quasi il 100% delle volte), voglio qualcosa di diverso da un'espressione richiesta. E nel caso estremamente raro che volevo un'espressione obbligatoria per i vincoli ad hoc, non mi dispiace davvero scrivere la parola due volte. È un chiaro indicatore che non ho sviluppato un'astrazione sufficientemente solida per il modello. (Perché se avessi avuto, avrebbe un nome.)

Avrei potuto scegliere di fare in modo che i requisiti introducessero un'espressione obbligatoria. In realtà è peggio, perché praticamente tutti i tuoi vincoli inizierebbero ad apparire così:

template<typename T>
  requires { requires Eq<T>; }
void f(T a, T b);

Qui, il secondo requisito è chiamato requisito nidificato; valuta la sua espressione (non viene valutato altro codice nel blocco dell'espressione richiesta). Penso che questo sia molto peggio dello status quo. Ora, puoi scrivere richiede due volte ovunque.

Avrei anche potuto usare più parole chiave. Questo è un problema a sé stante --- e non si tratta solo di spargimento di biciclette. Potrebbe esserci un modo per "ridistribuire" le parole chiave per evitare la duplicazione, ma non ho pensato seriamente. Ma questo non cambia davvero l'essenza del problema.


-10

Perché stai dicendo che una cosa A ha un requisito B e il requisito B ha un requisito C.

La cosa A richiede B che a sua volta richiede C.

La stessa clausola "richiede" richiede qualcosa.

Hai la cosa A (richiede B (richiede C)).

Meh. :)


4
Ma secondo le altre risposte, la prima e la seconda requiresnon sono concettualmente la stessa cosa (una è una clausola, l'altra un'espressione). In effetti, se capisco correttamente, i due insiemi di ()in requires (requires (T x) { x + x; })hanno significati molto diversi (l'esterno è facoltativo e contiene sempre una constexpr booleana; l'interno è una parte obbligatoria dell'introduzione di un'espressione richiesta e non consente espressioni effettive).
Max Langhof,

2
@MaxLanghof Stai dicendo che i requisiti differiscono? : D
Corse di leggerezza in orbita,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.