Почему мы требуем требует требует? - PullRequest
0 голосов
/ 15 января 2019

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

A require-expression также можно использовать в require-выражение ([temp]) в качестве способа записи специальных ограничений для аргументов шаблона, таких как приведенный ниже:

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

Первый из них вводит require-claus , а второй вводит require-expression .

Какова техническая причина, по которой нужно второе ключевое слово requires? Почему мы не можем просто разрешить писать:

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

(Примечание: пожалуйста, не отвечайте, что грамматика requires это)

Ответы [ 5 ]

0 голосов
/ 16 января 2019

Я нашел комментарий от Эндрю Саттона (одного из авторов Концепции, который реализовал его в gcc), который был очень полезен в этом отношении, поэтому я подумал, что просто приведу его здесь -entirety:

Не так давно требуются выражения (фраза, введенная вторым требованием) не была разрешена в выражениях ограничения (фраза, введенная первым требованием). Это может появиться только в определениях понятий. Фактически, это именно то, что предлагается в разделе того документа, где появляется это утверждение.

Однако в 2016 году было предложено ослабить это ограничение [Примечание редактора: P0266 ]. Обратите внимание на зачеркнутый пункт 4 в разделе 4 документа. И таким образом родился, требует требует.

Честно говоря, я никогда не применял это ограничение в GCC, поэтому это всегда было возможно. Я думаю, что Уолтер, возможно, обнаружил это и нашел это полезным, что привело к этой статье.

Чтобы никто не подумал, что я не чувствителен к письму, требуется дважды, я потратил некоторое время, пытаясь определить, можно ли это упростить. Краткий ответ: нет.

Проблема в том, что есть две грамматические конструкции, которые необходимо ввести после списка параметров шаблона: очень часто выражение ограничения (например, P && Q) и иногда синтаксические требования (например, requires (T a) { ... }). Это называется обязательным выражением.

Первое требует введения ограничения. Второе требование вводит выражение-требование. Это просто способ грамматики. Я совсем не смущаюсь.

В какой-то момент я пытался свести их к одному требованию. К сожалению, это приводит к серьезным проблемам с анализом. Вы не можете легко сказать, например, если ( после запроса означает вложенное подвыражение или список параметров. Я не верю, что существует идеальное устранение неоднозначности этих синтаксисов (см. Обоснование унифицированного синтаксиса инициализации; эта проблема также существует).

Итак, вы делаете выбор: make требует ввести выражение (как оно делает сейчас) или заставить его ввести параметризованный список требований.

Я выбрал текущий подход, потому что большую часть времени (как почти в 100% случаев) я хочу что-то иное, чем выражение-требование. И в чрезвычайно редком случае я хотел получить выражение для специальных ограничений, я действительно не против написать слово дважды. Это очевидный показатель того, что я не разработал достаточно надежную абстракцию для шаблона. (Потому что, если бы у меня было, у него было бы имя.)

Я мог бы сделать так, чтобы требование требовало введения выражения требования. Это на самом деле хуже, потому что практически все ваши ограничения начнут выглядеть так:

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

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

Я мог бы также использовать больше ключевых слов. Это проблема сама по себе - и это не просто потеря велосипеда. Возможно, есть способ «перераспределить» ключевые слова, чтобы избежать дублирования, но я не задумывался об этом серьезно. Но это не меняет сути проблемы.

0 голосов
/ 15 января 2019

Ситуация в точности аналогична noexcept(noexcept(...)).Конечно, это больше похоже на плохую вещь, чем на хорошую, но позвольте мне объяснить.:) Начнем с того, что вы уже знаете:

C ++ 11 имеет "noexcept -классы" и "noexcept -выражения".Они делают разные вещи.

  • A noexcept -класс говорит: «Эта функция не должна быть исключена, когда ... (некоторое условие)».Он идет по объявлению функции, принимает логический параметр и вызывает поведенческое изменение в объявленной функции.

  • A noexcept -выражение говорит: «Компилятор, , скажите, пожалуйста,мне, если (какое-то выражение) нет, кроме ".Это само по себе логическое выражение.У него нет «побочных эффектов» на поведение программы - он просто запрашивает у компилятора ответ на вопрос «да / нет».«Является ли это выражение исключением?»

Мы можем вкладывать выражение noexcept в выражение noexcept, но обычно мы считаем его плохим стилемсделайте это.

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

Считается лучшим стилем для инкапсуляции noexcept -выражения в тип-черте.«requires -классы» и «requires -выражения».Они делают разные вещи.

  • A requires -класс говорит: «Эта функция должна участвовать в разрешении перегрузки, когда ... (некоторое условие)».Он идет по объявлению функции, принимает логический параметр и вызывает поведенческое изменение в объявленной функции.

  • A requires - выражение говорит: «Компилятор, , скажите, пожалуйста,мне, правильно ли сформирован (некоторый набор выражений). "Это само по себе логическое выражение.У него нет «побочных эффектов» на поведение программы - он просто запрашивает у компилятора ответ на вопрос «да / нет».«Правильно ли сформировано это выражение?»

Мы можем вкладывать выражение 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 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
0 голосов
/ 15 января 2019

Это потому, что этого требует грамматика. Это делает.

Ограничение requires не должно использовать выражение requires. Он может использовать любое более или менее произвольное логическое константное выражение. Следовательно, requires (foo) должно быть законным requires ограничением.

A requires выражение (то, что проверяет, соответствуют ли определенные вещи определенным ограничениям), - это отдельная конструкция; это просто введено тем же ключевым словом. requires (foo f) будет началом действительного requires выражения.

То, что вы хотите, это то, что если вы используете requires в месте, которое принимает ограничения, вы должны иметь возможность сделать «ограничение + выражение» из предложения requires.

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

Учтите это:

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

Если foo является типом, тогда (foo) является списком параметров выражения требуется, и все в {} не является телом функции, но телом этого выражения requires. В противном случае foo является выражением в предложении requires.

Ну, вы могли бы сказать, что компилятор должен просто выяснить, что является foo первым. Но C ++ на самом деле не нравится, когда базовый акт анализа последовательности токенов требует, чтобы компилятор выяснил, что означают эти идентификаторы, прежде чем он сможет понять токены. Да, C ++ является контекстно-зависимым, так что это происходит. Но комитет предпочитает избегать этого там, где это возможно.

Так что да, это грамматика.

0 голосов
/ 15 января 2019

Потому что вы говорите, что у вещи A есть требование B, а у требования B есть требование C.

вещь А требует B, который в свою очередь требует C.

Само предложение "требует" требует чего-то.

У вас есть вещь A (требующая B (требующая C)).

Мех. :)

0 голосов
/ 15 января 2019

Я думаю, страница концепций 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; }

, о чем вы спрашиваете.

...