Чому нам потрібні необхідні потреби?


161

Один із куточків концепцій C ++ 20 - це те, що існують певні ситуації, в яких потрібно писати requires requires. Наприклад, цей приклад з [expr.prim.req] / 3 :

Потрібно вираз також може бути використано в вимагаєте-положення ([Темп]) в якості способу написання спеціальних обмежень на аргументах шаблону , такі як один нижче:

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

Перший вимагає вводити вимогу-застереження , а другий вводить вираз-вимога .

Яка технічна причина потребує цього другого requiresключового слова? Чому ми не можемо просто дозволити писати:

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

(Примітка. Будь ласка, не відповідайте, що це граматика requires)


25
Пропозиція: "Чи потрібне щось, що вимагає?". Більш серйозно, у мене є думка, що це та сама причина позаду noexcept(noexcept(...)).
Квентін

11
На requiresмою думку, це два омоніми: вони виглядають однаково, пишуть однаково, пахнуть однаково, але суттєво відрізняються. Якби я запропонував виправити, я б запропонував перейменувати один із них.
ВАТ

5
@YSC - co_requires? (Вибачте, не втримався).
StoryTeller - Невідповідач Моніки

122
Де зупиниться божевілля? Наступне, що ви знаєте, у нас буде long long.
Eljay

8
@StoryTeller: "Потрібно вимагати?" був би ще більш алітеративним !!
PW

Відповіді:


81

Це тому, що цього вимагає граматика. Це робить.

requiresОбмеження не повинно використовувати requiresвираз. Він може використовувати будь-яке більш-менш довільне булеве постійне вираження. Тому requires (foo)повинно бути законним requiresобмеженням.

requires Вираз (та річ , що тести деякі речі йдуть певні обмеження) є ідеальної відправною точкою конструкт; він просто введений тим самим ключовим словом. requires (foo f)буде початком дійсного requiresвиразу.

Що ви хочете, це те, що якщо ви використовуєте requiresмісце, яке приймає обмеження, вам слід мати змогу зробити "обмеження + вираз" з requiresпункту.

Отже, ось питання: якщо ви ставите requires (foo)в місце, яке підходить для необхідного обмеження ... як далеко повинен пройти аналізатор, перш ніж він зможе зрозуміти, що це потрібне обмеження, а не обмеження + вираз так, як ви цього хочете. бути?

Врахуйте це:

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

Якщо fooце тип, то (foo)це список параметрів необхідного виразу, і все, що знаходиться в, {}- це не тіло функції, а тіло цього requiresвиразу. В іншому випадку foo- це вираз у requiresпункті.

Ну, можна сказати, що компілятор повинен просто з'ясувати, що fooє першим. Але C ++ дійсно не подобається, коли основний акт розбору послідовності лексем вимагає, щоб компілятор розібрався, що означають ці ідентифікатори, перш ніж він може мати сенс лексем. Так, C ++ є залежним від контексту, тому це і відбувається. Але комітет вважає за краще уникати цього, де це можливо.

Так що так, це граматика.


2
Чи є сенс мати список параметрів з типом, але без імені?
NathanOliver

3
@Quentin: У граматиці С ++, безумовно, є випадки контекстної залежності. Але комітет насправді намагається мінімізувати це, і їм точно не подобається додавати більше .
Нікол

3
@RobertAndrzejuk: Якщо requiresз'являється після <>набору аргументів шаблону або після списку параметрів функції, це застереження. Якщо requiresз'являється, де вираз є дійсним, то це вираз, який вимагається. Це може бути визначено структурою дерева розбору, а не вмістом дерева розбору (специфіка того, як визначається ідентифікатор, буде вмістом дерева).
Нікол

6
@RobertAndrzejuk: Зрозуміло, що вираз-вираз може використати інше ключове слово. Але ключові слова мають величезні витрати на C ++, оскільки вони мають потенціал зламати будь-яку програму, яка використовувала ідентифікатор, який став ключовим словом. Пропозиція концепцій вже представила два ключові слова: conceptі requires. Впровадження третього, коли другий зможе охопити обидва випадки без граматичних питань та мало проблем із користувачем, просто марно. Зрештою, єдиною візуальною проблемою є те, що ключове слово повторюється двічі.
Нікол

3
@RobertAndrzejuk так чи інакше погана практика, таким чином обмежуючи такі обмеження, оскільки ви не отримуєте підписки, як ніби ви написали концепцію. Тож прийняття ідентифікатора для такої нерекомендованої функції не є хорошою ідеєю.
Rakete1111

60

Ситуація точно аналогічна noexcept(noexcept(...)). Звичайно, це звучить більше як погана річ, ніж гарна, але дозвольте мені пояснити. :) Почнемо з того, що ви вже знаєте:

C ++ 11 має " noexcept-клаузи" та "-вираження" noexcept. Вони роблять різні речі.

  • noexcept-Clause каже: «Ця функція повинна бути noexcept , коли ... (деякий умова).» Він переходить до оголошення функції, приймає булевий параметр і викликає зміну поведінки в оголошеній функції.

  • noexcept-Expression каже, «Compiler, скажіть , будь ласка , є чи (певне підґрунтя) є noexcept.» Це сам по собі булевий вираз. У нього немає "побічних ефектів" на поведінку програми - це просто просити у компілятора відповідь на питання "так / ні". "Це вираз не є винятком?"

Ми можемо вкласти noexcept-вираз усередині noexcept-клаузи, але ми зазвичай вважаємо це поганим стилем.

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

Вважається кращим стилем інкапсуляцію noexcept-вираз у типовій рисі.

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

Робочий проект C ++ 2a містить " requires-класи" та "-вираження" requires. Вони роблять різні речі.

  • requires-Clause каже: «Ця функція повинна приймати участь у вирішенні перевантаження , коли ... (деякі умови).» Він переходить до оголошення функції, приймає булевий параметр і викликає зміну поведінки в оголошеній функції.

  • requires-Expression каже, «Compiler, скажіть , будь ласка , є чи це (деякий набір виразів) добре сформовані.» Це сам по собі булевий вираз. У нього немає "побічних ефектів" на поведінку програми - це просто просити у компілятора відповідь на питання "так / ні". "Чи добре сформований цей вираз?"

Ми можемо вкласти requires-вираз усередині requires-клаузи, але ми зазвичай вважаємо це поганим стилем.

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

Вважається кращим стилем інкапсуляції requires-вираз у рисі типу ...

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

... або в концепції (робочий проект C ++ 2a).

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
Я не дуже купую цей аргумент. noexceptє проблема , що noexcept(f())може означати або інтерпретувати f()як логічні , який ми використовуємо , щоб встановити специфікації або перевірити , є чи не f()є noexcept. requiresне має цієї неоднозначності, тому що вирази, які перевіряють на достовірність, вже мають бути введені за допомогою {}s. Після цього аргумент в основному "граматика так говорить".
Баррі

@Barry: Дивіться цей коментар . Здається {}, необов’язкові.
Ерік

1
@Eric Це {}не обов'язково, це не те, що показує коментар. Однак це чудовий коментар, що демонструє неоднозначність розбору. Ймовірно, прийняв би цей коментар (з деяким поясненням) як окрему відповідь
Баррі

1
requires is_nothrow_incrable_v<T>;повинно бутиrequires is_incrable_v<T>;
Руслан

16

Я думаю , що сторінка концепцій cppreference пояснює це. Я можу пояснити "математикою" так, щоб сказати, чому це повинно бути таким:

Якщо ви хочете визначити поняття, зробіть це:

template<typename T>
concept 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; }

Тепер, якщо ви не хочете визначати поняття окремо, я думаю, все, що вам потрібно зробити, - це деяка підміна. Візьміть цю частину requires (T x) { x + x; };і замініть її Addable<T>, і ви отримаєте:

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

про що ви питаєте.


4
Я не думаю, що це питання задається. Це пояснює граматику, більш-менш.
Перехожий

Але чому ти не можеш template<typename T> requires (T x) { x + x; } та вимагати, що вимагати, може бути як пропозицією, так і виразом?
NathanOliver

2
@NathanOliver: Тому що ви змушуєте компілятор інтерпретувати одну конструкцію як іншу. АrequiresПункт його обмеження не повинен бути requires-expression. Це лише одне можливе його використання.
Нікол

2
@TheQuantumPhysicist Що я отримував зі своїм коментарем, ця відповідь просто пояснює синтаксис. Не те, що насправді має технічна причина requires requires. Вони могли додати щось до граматики, щоб дозволити, template<typename T> requires (T x) { x + x; }але вони цього не зробили. Баррі хоче знати, чому вони цього не зробили
NathanOliver

3
Якщо ми справді гратимемо тут - неоднозначність грамматики, добре, я кусаю. godbolt.org/z/i6n8kM template<class T> void f(T) requires requires(T (x)) { (void)x; }; означає щось інше, якщо ви видалите один з requiresес.
Квомплусон

12

Я знайшов коментар від Ендрю Саттона (одного з авторів Концепцій, який реалізував його в gcc) як дуже корисний у цьому плані, тому я подумав, що я просто процитую його тут у повному обсязі:

Не так давно вимоги-вирази (словосполучення, введене другим вимагає) не були дозволені в обмеженнях-виразах (словосполучення, введене першим, вимагає). Це могло з'явитися лише у поняттях визначення. Насправді це саме те, що пропонується в розділі цього документу, де ця заява з’являється.

Однак у 2016 році була пропозиція зняти це обмеження [Примітка редактора: P0266 ]. Зауважте підкреслення пункту 4 розділу 4 цього документа. І таким чином народився вимагає вимагає.

По правді кажучи, я ніколи фактично не застосовував це обмеження в GCC, тому це завжди було можливо. Я думаю, що Уолтер, можливо, це виявив і вважав корисним, що веде до цієї роботи.

Щоб хтось не думав, що я не чутливий до письма вимагає двічі, я витратив деякий час, намагаючись визначити, чи можна це спростити. Коротка відповідь: ні.

Проблема полягає в тому, що є два граматичні конструкції, які потрібно ввести після списку параметрів шаблону: дуже часто вираз обмеження (як P && Q) та періодично синтаксичні вимоги (подібні requires (T a) { ... }). Це називається вимогою-виразом.

Перший вимагає введення обмеження. Друга вимагає введення вираз-вимагає. Саме так складається граматика. Я взагалі не вважаю це заплутаним.

Я намагався, в один момент, звести їх до єдиної потреби. На жаль, це призводить до деяких серйозно складних проблем розбору. Ви не можете легко сказати, наприклад, якщо a( після вимоги позначає вкладений піддекспресія або список параметрів. Я не вірю, що ці синтаксиси є ідеальними (див. Обґрунтування рівномірного синтаксису ініціалізації; ця проблема теж є).

Отже, ви робите вибір: make вимагає ввести вираз (як це робиться зараз) або змусити його ввести параметризований список вимог.

Я вибрав поточний підхід, тому що більшу частину часу (як майже у 100% часу) я хочу чогось іншого, ніж вимагати висловлення. І в надзвичайно рідкісному випадку я захотів висловити вимоги для спеціальних обмежень, я дійсно не проти написати слово два рази. Це очевидний показник того, що я не розробив достатньо звукової абстракції для шаблону. (Тому що, якби я мав, це мала б назву.)

Я міг би обрати вимоги, щоб запровадити вираз-вираз. Це насправді гірше, адже практично всі ваші обмеження почали виглядати так:

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

Тут друга вимога називається вкладеною вимогою; він оцінює своє вираження (інший код у блоці вимагає виразу не оцінюється). Я думаю, це набагато гірше, ніж статус-кво. Тепер вам потрібно писати двічі скрізь.

Я також міг би використовувати більше ключових слів. Це сама по собі проблема --- і це не лише прокладка велосипедів. Можливо, є спосіб "перерозподілити" ключові слова, щоб уникнути дублювання, але я не задумувався над такою серйозною думкою. Але це насправді не змінює суті проблеми.


-10

Тому що ви говорите, що річ A має вимогу B, а вимога B має вимогу C.

Реч А вимагає В, що, в свою чергу, вимагає С.

Застереження "вимагає" саме по собі щось вимагає.

У вас є річ A (вимагає B (вимагає C)).

Мех. :)


4
Але згідно з іншими відповідями, перша та друга - requiresце концептуально не одне і те ж (одна - це пропозиція, одна - вираз). Насправді, якщо я правильно розумію, два набори ()в requires (requires (T x) { x + x; })мають дуже різні значення (зовнішня істота є необов’язковою і завжди містить булевий конспект; внутрішня є обов'язковою частиною введення виразів, що вимагають, і не допускає дійсних виразів).
Макс Ланггоф

2
@MaxLanghof Ви хочете сказати, що вимоги відрізняються? : D
Гонки легкості на орбіті
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.