为什么我们需要需求?

时间:2019-01-15 14:33:43

标签: c++ c++-concepts c++20

C ++ 20概念的一个角落是在某些情况下您必须编写requires requires。例如,来自[expr.prim.req]/3的以下示例:

  

requires-表达式也可以在 requires-clause ([temp])中使用,作为对模板参数(例如一个)写临时约束的一种方式下方:

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

第一个require引入了 requires-clause ,第二个require引入了 requires-expression

需要第二个requires关键字背后的技术原因是什么?为什么我们不能只允许写作:

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

(注意:请不要回答语法requires

5 个答案:

答案 0 :(得分:76)

这是因为语法要求它。是的。

requires约束不必使用requires表达式。它可以使用或多或少的任意布尔常量表达式。因此,requires (foo)必须是合法的requires约束。

requires 表达式(用于测试某些事物是否遵循特定约束的事物)是一种独特的结构;它是由相同的关键字引入的。 requires (foo f)将是有效requires表达式的开头。

您想要的是,如果您在接受约束的地方使用requires,则应该能够在requires子句中做出“约束+表达式”。

所以这是一个问题:如果将requires (foo)放在适合需求约束的地方...解析器必须走多远才能意识到这是一个需求约束[em] 而不是您想要的约束+表达式?

考虑一下:

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

如果foo是类型,则(foo)是require表达式的参数列表,并且{}中的所有内容都不是函数的主体,而是那个{的主体{1}}表达式。否则,requiresfoo子句中的表达式。

好吧,您可以说编译器应该只弄清楚requires是什么。但是,当解析标记序列的基本操作要求编译器在理解标记之前先弄清楚这些标识符的含义时,C ++真的不喜欢它。是的,C ++是上下文相关的,因此确实会发生这种情况。但是委员会希望尽可能避免这种情况。

是的,这是语法。

答案 1 :(得分:57)

情况与noexcept(noexcept(...))完全相似。当然,这听起来好事多过一件坏事,但让我解释一下。 :)我们将从您已经知道的内容开始:

C ++ 11具有“ noexcept子句”和“ noexcept表达式”。他们做不同的事情。

  • 一个noexcept子句说:“此功能不应在……时出现(某些情况)。”它进行一个函数声明,接受一个布尔值参数,并导致声明的函数的行为发生变化。

  • 一个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子句说:“此函数应该在…………(某些情况)下参与重载解析。”它进行一个函数声明,接受一个布尔值参数,并导致声明的函数的行为发生变化。

  • 一个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工作草案)概念中。

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

答案 2 :(得分:15)

我认为cppreference's concepts page对此进行了解释。我可以用“数学”解释一下,为什么必须这样:

如果要定义概念,请执行以下操作:

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; }

这就是您要问的问题。

答案 3 :(得分:10)

我发现安德鲁·萨顿(Andrew Sutton)(在gcc中实现该概念的作者之一)的a comment在这方面非常有帮助,所以我想在这里引用它的全部内容:

  

不久前,约束表达式(第一个require引入的短语)中不允许使用require-expressions(由第二个require引入的短语)。它只能出现在概念定义中。实际上,这正是该声明出现的那部分中的建议。

     

但是,在2016年,有人提议放宽该限制[编者注:P0266]。注意本文第4节第4段的删除线。因此,出生就需要要求。

     

说实话,我从未在GCC中真正实施过该限制,因此这一直是可能的。我认为Walter可能已经发现了这一点,并发现它很有用,从而得出了这篇论文。

     

以免有人认为我对写作不敏感,需要两次,我确实花了一些时间来确定是否可以简化。简短的回答:不。

     

问题在于,在模板参数列表之后需要引入两种语法构造:非常常见的是约束表达式(如P && Q)和偶尔的语法要求(如requires (T a) { ... })。这就是所谓的require-expression。

     

第一个要求引入约束。第二个require引入了require-expression。那就是语法的构成方式。我一点都不觉得混乱。

     

我曾试图将这些折叠成一个需求。不幸的是,这导致了一些非常困难的解析问题。您不容易知道,例如,在require后面的(表示嵌套的子表达式或参数列表。我不认为这些语法可以完全消除歧义(请参阅统一初始化语法的原理;也存在此问题)。

     

因此,您可以选择:make需要引入一个表达式(如现在所做的那样)或使其引入参数化的需求列表。

     

我之所以选择当前的方法,是因为在大多数情况下(大约100%的时间),我需要的不是require-expression。在非常罕见的情况下,我确实想要临时约束的require-expression,我真的不介意两次写这个词。这显然表明我尚未为模板开发出足够完善的抽象。 (因为如果有的话,它将有一个名字。)

     

我本可以选择将需求引入需求表达式。这实际上更糟,因为实际上您的所有约束都会开始看起来像这样:

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

在这里,第二个要求称为嵌套要求;它评估其表达式(不评估require-expression块中的其他代码)。我认为这比现状差很多。现在,您到处都需要编写两次。

     

我还可以使用更多关键字。就其本身而言,这是一个问题-不仅是自行车脱落。可能有一种方法可以“重新分配”关键字以避免重复,但是我没有认真考虑过。但这并不能真正改变问题的实质。

答案 4 :(得分:-10)

因为您说的是事物A具有需求B,而需求B具有需求C。

事物A需要B,而事物B又需要C。

“ requires”子句本身需要一些内容。

您有东西A(要求B(要求C))。

嗯。 :)