为什么我们需要需求?


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

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

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

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

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


25
建议:“是否有任何需要的条件?”。更严重的是,我有一种直觉,那就是背后的原因相同noexcept(noexcept(...))
昆汀

11
requires我认为这两个是同音异义词:它们看起来相同,拼写相同,气味相同,但是本质上是不同的。如果我建议修复,则建议重命名其中之一。
YSC

5
@YSC- co_requires吗?(对不起,无法抗拒)。
StoryTeller-Unslander Monica,

122
疯狂会在哪里停止?接下来的事情,我们将有long long
Eljay

8
@StoryTeller:“需要要求吗?” 本来会更加文盲!!
PW

Answers:


81

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

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

requires 表达(即东西测试某些事情是否遵循一定的约束)是一种独特的构建体; 它只是由相同的关键字引入的。requires (foo f)将是有效requires表达式的开始。

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

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

考虑一下:

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

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

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

是的,这是语法。


2
具有类型但不带名称的参数列表是否有意义?
NathanOliver

3
@Quentin:在C ++语法中肯定存在上下文相关的情况。但是委员会确实确实试图将其最小化,他们绝对不喜欢增加更多
Nicol Bolas

3
@RobertAndrzejuk:如果requires出现在一<>组模板参数之后或函数参数列表之后,则这是一个必要条款。如果requires出现表达式有效的地方,则它是一个require-expression。这可以由解析树的结构来确定,而不是由解析树的内容来确定(如何定义标识符的细节将是树的内容)。
Nicol Bolas

6
@RobertAndrzejuk:当然,require-expression可以使用其他关键字。但是关键字在C ++中具有巨大的成本,因为它们有可能破坏使用已成为关键字的标识符的任何程序。概念提案已经引入了两个关键字:conceptrequires。引入第三种方法,而第二种方法能够涵盖两种情况,而没有语法问题,并且几乎没有面向用户的问题,这是浪费的。毕竟,唯一的视觉问题是关键字恰巧重复了两次。
Nicol Bolas

3
@RobertAndrzejuk那样的内联约束无论如何都是不好的做法,因为您不会像编写一个概念那样得到包容。因此,将标识符用于如此低的使用率不推荐使用的功能不是一个好主意。
Rakete1111 '19

60

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

C ++ 11具有“ noexcept-clauses”和“ -expressions” noexcept。他们做不同的事情。

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

  • - 表达式noexcept表示:“编译器,请告诉我(某些表达式)是否例外。” 它本身是一个布尔表达式。它对程序的行为没有“副作用”,只是向编译器询问是/否问题的答案。“这个表情无例外吗?”

我们可以在- noexcept子句中嵌套noexcept-expression,但通常认为这样做很不好。

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

noexcept-expression 封装在类型特征中被认为是更好的样式。

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-clauses”和“ -expressions” requires。他们做不同的事情。

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

  • 一个requires-expression说,“编译器,请告诉我是否(一些表达式集)是良好的。” 它本身是一个布尔表达式。它对程序的行为没有“副作用”,只是向编译器询问是/否问题的答案。“这个表达形式是否正确?”

我们可以在- requires子句中嵌套requires-expression,但通常认为这样做很不好。

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

requires-expression 封装在类型特征中被认为是更好的样式...

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()noexceptrequires并没有这种歧义,因为已经使用{}s 引入了对其有效性进行检查的表达式。在那之后,论点基本上是“语法是这样的”。
巴里

@Barry:请参阅此评论。似乎{}是可选的。
艾瑞克(Eric)

1
@Eric {}不是可选的,不是该注释显示的内容。但是,这是一个很好的注释,表明了解析的歧义。可能会接受该评论(带有一些解释)作为独立答案
Barry

1
requires is_nothrow_incrable_v<T>;应该是requires is_incrable_v<T>;
Ruslan

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-as约束条款并不必须requires-expression。那只是它的一种可能用途。
Nicol Bolas

2
@TheQuantumPhysicist我的评论是,这个答案只是解释了语法。不是我们必须要考虑的实际技术原因requires requires。他们可以在语法中添加一些内容,template<typename T> requires (T x) { x + x; }但不允许这样做。巴里想知道为什么他们没有
-NathanOliver

3
如果我们在这里真的在寻找语法歧义,那么我会咬。如果您删除其中一个es,则godbolt.org/z/i6n8kM的 template<class T> void f(T) requires requires(T (x)) { (void)x; };含义有所不同requires
Quuxplusone

12

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

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

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

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

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

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

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

我曾试图将这些折叠成一个单一的需求。不幸的是,这导致了一些非常困难的解析问题。您无法轻易分辨,例如(,requires之后的a表示嵌套的子表达式或参数列表。我不认为这些语法有完美的歧义(请参阅统一初始化语法的原理;这里也存在此问题)。

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

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

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

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

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

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


-10

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

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

“需要”子句本身需要一些东西。

你有东西A(需要B(需要C))。

嗯 :)


4
但是根据其他答案,第一个和第二个requires在概念上不是同一件事(一个是子句,一个是表达式)。事实上,如果正确地明白这两组()requires (requires (T x) { x + x; })具有非常不同的含义(外侧是可选的并且总是含有一个布尔constexpr;内被引入的一个强制性的一部分的需要表达和使实际表达式)。
Max Langhof

2
@MaxLanghof您是说要求不同吗?:D
轻轨赛于
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.