Jednym z rogów koncepcji C ++ 20 jest to, że są pewne sytuacje, w których musisz pisać requires requires
. Na przykład ten przykład z [wyr.prim.req] / 3 :
Wymaga ekspresja może być również stosowany w wymaga-klauzula ([temp]) jako sposób pisania ograniczenia ad hoc dotyczący argumentów szablonu, takich jak ten poniżej:
template<typename T> requires requires (T x) { x + x; } T add(T a, T b) { return a + b; }
Pierwsza wymaga wprowadza klauzulę wymaga , a druga wprowadza wyrażenie wymaga .
Jaki jest techniczny powód, dla którego potrzebne jest drugie requires
słowo kluczowe? Dlaczego nie możemy po prostu pozwolić na pisanie:
template<typename T>
requires (T x) { x + x; }
T add(T a, T b) { return a + b; }
(Uwaga: proszę nie odpowiadać, że gramatyka requires
to)
c++
c++-concepts
c++20
Barry
źródło
źródło
noexcept(noexcept(...))
.requires
Moim zdaniem oba są homonimami: wyglądają tak samo, pisują tak samo, pachną tak samo, ale są z natury różne. Gdybym miał zasugerować poprawkę, sugerowałbym zmianę nazwy jednego z nich.co_requires
? (Przepraszam, nie mogłem się oprzeć).long long
.Odpowiedzi:
Dzieje się tak, ponieważ wymaga tego gramatyka. To robi.
requires
Ograniczenie nie trzeba używaćrequires
wyrazu. Może używać dowolnego mniej lub bardziej dowolnego logicznego wyrażenia stałego. Dlategorequires (foo)
musi być uzasadnionymrequires
ograniczeniem.requires
Ekspresji (to sprawa, że sprawdza, czy pewne rzeczy następują pewne ograniczenia) jest odrębną Konstrukt; jest po prostu wprowadzany tym samym słowem kluczowym.requires (foo f)
byłby początkiem prawidłowegorequires
wyrażenia.To, czego chcesz, to to, że jeśli używasz
requires
w miejscu, które akceptuje ograniczenia, powinieneś być w stanie zrobić „ograniczenie + wyrażenie” zrequires
klauzuli.Oto więc pytanie: jeśli umieścisz
requires (foo)
miejsce, które jest odpowiednie dla ograniczenia wymagań ... jak daleko parser musi się posunąć, zanim zda sobie sprawę, że jest to raczej ograniczenie wymagające niż ograniczenie + wyrażenie tak, jak chcesz być?Rozważ to:
Jeśli
foo
jest typem, to(foo)
jest listą parametrów wyrażenia wymaganego, a wszystko w elemencie{}
nie jest treścią funkcji, ale treściąrequires
wyrażenia. W przeciwnym raziefoo
jest wyrażeniem wrequires
klauzuli.Cóż, można powiedzieć, że kompilator powinien po prostu dowiedzieć się, co
foo
jest pierwsze. Ale C ++ naprawdę nie lubi, kiedy podstawowa czynność parsowania sekwencji tokenów wymaga, aby kompilator zorientował się, co oznaczają te identyfikatory, zanim będzie mógł nadać sens tokenom. Tak, C ++ jest zależny od kontekstu, więc tak się dzieje. Ale komisja woli tego unikać, jeśli to możliwe.Więc tak, to gramatyka.
źródło
requires
pojawia się po<>
zestawie argumentów szablonu lub po liście parametrów funkcji, to jest to klauzula wymaga. Jeślirequires
pojawia się tam, gdzie wyrażenie jest prawidłowe, jest to wyrażenie wymagające. Można to określić na podstawie struktury drzewa analizy, a nie zawartości drzewa analizy (specyfiką definiowania identyfikatora byłaby zawartość drzewa).concept
irequires
. Wprowadzenie trzeciego, kiedy drugi byłby w stanie objąć oba przypadki bez problemów gramatycznych i kilku problemów napotykanych przez użytkowników, jest po prostu marnotrawstwem. W końcu jedynym problemem wizualnym jest to, że słowo kluczowe powtarza się dwukrotnie.Sytuacja jest dokładnie analogiczna do
noexcept(noexcept(...))
. Jasne, brzmi to bardziej jak zła rzecz niż dobra, ale pozwól mi wyjaśnić. :) Zaczniemy od tego, co już wiesz:C ++ 11 ma „
noexcept
-clauses” i „noexcept
-expressions”. Robią różne rzeczy.noexcept
Klauzula- A mówi: „Ta funkcja nie powinna być wyjątkiem, gdy ... (jakiś warunek)”. Przechodzi do deklaracji funkcji, przyjmuje parametr boolowski i powoduje zmianę zachowania w zadeklarowanej funkcji.A
noexcept
-expression mówi: „Kompilatorze, powiedz mi, czy (jakieś wyrażenie) jest noexcept”. Samo jest wyrażeniem logicznym. Nie ma żadnych "skutków ubocznych" w zachowaniu programu - po prostu prosi kompilator o odpowiedź na pytanie tak / nie. "Czy to wyrażenie noexcept?"My może zagnieździć się
noexcept
-expression wewnątrznoexcept
-clause, ale zazwyczaj uważają ją złe stylu, aby to zrobić.Uważa się, że lepszy styl zawiera
noexcept
-wyrażenie w cechach typu.Robocza
requires
wersja robocza C ++ 2a ma „ -clauses” i „requires
-expressions”. Robią różne rzeczy.A
requires
-clause mówi: „Ta funkcja powinna brać udział w rozwiązywaniu problemu przeciążenia, gdy ... (jakiś warunek)”. Przechodzi do deklaracji funkcji, przyjmuje parametr boolowski i powoduje zmianę zachowania w zadeklarowanej funkcji.A
requires
-expression mówi: „Kompilatorze, powiedz mi, czy (jakiś zestaw wyrażeń) jest poprawnie sformułowany”. Samo jest wyrażeniem logicznym. Nie ma żadnych "skutków ubocznych" w zachowaniu programu - po prostu prosi kompilator o odpowiedź na pytanie tak / nie. "Czy to wyrażenie jest dobrze sformułowane?"My może zagnieździć się
requires
-expression wewnątrzrequires
-clause, ale zazwyczaj uważają ją złe stylu, aby to zrobić.Uważa się, że lepszy styl hermetyzowania
requires
wyrażenia w cechach typu ...... lub w koncepcji (C ++ 2a Working Draft).
źródło
noexcept
ma problem, którynoexcept(f())
może oznaczać albo zinterpretowaćf()
jako wartość logiczną, której używamy do ustawienia specyfikacji, albo sprawdzić, czy takf()
jestnoexcept
.requires
nie ma tej niejednoznaczności, ponieważ wyrażenia, które sprawdza poprawność, muszą być już wprowadzone za pomocą{}
s. Następnie argument jest w zasadzie „tak mówi gramatyka”.{}
to, że są opcjonalne.{}
nie są opcjonalne, nie to pokazuje ten komentarz. Jest to jednak świetny komentarz pokazujący niejednoznaczność analizowania. Prawdopodobnie zaakceptowałby ten komentarz (z pewnym wyjaśnieniem) jako samodzielną odpowiedźrequires is_nothrow_incrable_v<T>;
powinno byćrequires is_incrable_v<T>;
Myślę, że strona pojęć cppreference wyjaśnia to. Mogę wyjaśnić za pomocą „matematyki”, że tak powiem, dlaczego to musi wyglądać tak:
Jeśli chcesz zdefiniować pojęcie, robisz to:
Jeśli chcesz zadeklarować funkcję używającą tego pojęcia, wykonaj następujące czynności:
Jeśli nie chcesz osobno definiować tego pojęcia, myślę, że wszystko, co musisz zrobić, to jakąś substytucję. Weź tę część
requires (T x) { x + x; };
i wymieńAddable<T>
część, a otrzymasz:o to właśnie pytasz.
źródło
template<typename T> requires (T x) { x + x; }
i wymagać, aby wymaganie mogło być zarówno klauzulą, jak i wyrażeniem?requires
Klauzula -as-ograniczenia nie muszą byćrequires
-expression. To tylko jedno z możliwych zastosowań.requires requires
. Mogli dodać coś do gramatyki,template<typename T> requires (T x) { x + x; }
ale nie zrobili tego. Barry chce wiedzieć, dlaczego tego nie zrobilitemplate<class T> void f(T) requires requires(T (x)) { (void)x; };
oznacza coś innego, jeśli usuniesz jeden zrequires
es.Znalazłem komentarz Andrew Suttona (jednego z autorów Concepts, który zaimplementował go w gcc), który był bardzo pomocny w tym względzie, więc pomyślałem, że zacytuję go tutaj w całości:
źródło
Ponieważ mówisz, że rzecz A ma wymaganie B, a wymaganie B ma wymaganie C.
Rzecz A wymaga B, która z kolei wymaga C.
Sama klauzula „wymaga” czegoś wymaga.
Masz rzecz A (wymagająca B (wymagająca C)).
Meh. :)
źródło
requires
nie są koncepcyjnie tym samym (jedna jest klauzulą, druga wyrażeniem). W rzeczywistości, jeśli dobrze rozumiem, te dwa zbiory()
inrequires (requires (T x) { x + x; })
mają bardzo różne znaczenia (zewnętrzna jest opcjonalna i zawsze zawiera boolean constexpr; wewnętrzna jest obowiązkową częścią wprowadzenia wymaganego wyrażenia i nie zezwala na rzeczywiste wyrażenia).