为什么我们需要需要?

新手上路,请多包涵

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

requires-expression 也可以在 requires-clause ([temp]) 中使用,作为在模板参数上编写临时约束的一种方式,如下所示:

>  template<typename T>
>   requires requires (T x) { x + x; }
>     T add(T a, T b) { return a + b; }
>
> ```
>
> 第一个 requires 引入了 _requires-clause_ ,第二个引入了 _requires-expression_ 。

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

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

”`

(注意:请不要回答那个语法 requires 它)

原文由 Barry 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 370
2 个回答

这是因为语法需要它。确实如此。

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

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

你想要的是,如果你在接受约束的地方使用 requires ,你应该能够从 requires 子句中创建一个“约束+表达式”。

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

考虑一下:

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

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

好吧,你可以说编译器应该首先弄清楚 foo 是什么。但是 C++ 真的 不喜欢它,因为解析标记序列的基本行为要求编译器在理解标记之前弄清楚这些标识符的含义。是的,C++ 是上下文相关的,所以这确实发生了。但委员会倾向于尽可能避免这种情况。

所以是的,这是语法。

原文由 Nicol Bolas 发布,翻译遵循 CC BY-SA 4.0 许可协议

我认为 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; }

这解释了机制。如果我们将语言更改为接受单个 requires 作为 requires requires 的简写,则最好用一个模棱两可的例子来说明 _为什么_。

 constexpr int x = 42;

template<class T>
void f(T) requires(T (x)) { (void)x; };

template<class T>
void g(T) requires requires(T (x)) { (void)x; };

int main(){
    g<bool>(0);
}

在 Godbolt 中查看以查看编译器警告,但请注意 Godbolt 不会尝试链接步骤,在这种情况下会失败。

f 和 g 之间的唯一区别是 ‘requires’ 加倍。然而 f 和 g 之间的语义差异是巨大的:

  • g 只是一个函数声明, f 是一个完整的定义
  • f 只接受 bool,g 接受所有可转换为 void 的类型
  • g 用它自己的(用括号括起来的)x 遮蔽 x,但是
  • f 将全局 x 转换为给定类型 T

显然,我们不希望编译器自动将一个更改为另一个。这可以通过对 requires 的两个含义使用单独的关键字来解决,但是在可能的情况下,C++ 会尝试在不引入太多新关键字的情况下发展,因为这会破坏旧程序。

原文由 The Quantum Physicist 发布,翻译遵循 CC BY-SA 4.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题