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

require -表达式也可以用在require -子句([temp])中,作为对模板参数编写特殊约束的一种方式,如下所示: 模板< typename T > require require (T x) {x + x;} T add(T a, T b) {return a + b;} 第一个require引入了require子句,第二个require引入了require表达式。

需要第二个“需要”关键字背后的技术原因是什么?为什么我们不允许这样写:

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

(注意:请不要回答语法要求)


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

现在如果你不想单独定义这个概念,我想你只需要做一些替换。取这部分要求(T x) {x + x;};并替换Addable<T>部分,你会得到:

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

这就解释了机制。如果我们改变语言,接受一个require作为require的简写,就会产生歧义,用这个例子最好地说明了原因。

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

显然,我们不希望编译器自动将其中一个转换为另一个。这个问题本可以通过使用一个单独的关键字来表示require的两个含义来解决,但是在可能的情况下,c++尝试在不引入太多新关键字的情况下进行发展,因为这会破坏旧的程序。


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

require约束不一定要使用require表达式。它可以使用任意布尔常数表达式。因此,require (foo)必须是一个合法的require约束。

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

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

那么问题来了:如果你把require (foo)放到适合require约束的地方……解析器要走多远才能意识到这是一个require约束,而不是您想要的约束+表达式?

考虑一下:

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

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

你可以说编译器应该先找出foo是什么。但是c++真的不喜欢解析一系列标记的基本行为要求编译器在理解这些标记之前先弄清楚这些标识符的含义。是的,c++是上下文敏感的,所以这种情况确实会发生。但委员会倾向于尽可能避免使用它。

是的,这是语法问题。


因为你在说一个东西a有一个需求B,而需求B有一个需求C。

A需要B, B又需要C。

"requires"从句本身要求某事。

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

咩。:)


这种情况完全类似于noexcept(noexcept(…))。当然,这听起来更像是一件坏事而不是好事,但让我解释一下。我们将从你已经知道的开始:

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

noexcept-子句说:“当…(一些条件)”。它继续进行函数声明,接受一个布尔参数,并在声明的函数中引起行为更改。 noexcept表达式说:“编译器,请告诉我(某个表达式)是否为noexcept。”它本身是一个布尔表达式。它对程序的行为没有“副作用”——它只是向编译器询问是/否问题的答案。“这个表达是没有例外吗?”

我们可以在noexcept-子句中嵌套noexcept-表达式,但我们通常认为这样做是不好的风格。

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

将noexcept-表达式封装在类型-trait中被认为是更好的风格。

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工作草案有“要求-条款”和“要求-表达式”。他们做不同的事情。

A requires-clause says, "This function should participate in overload resolution when... (some condition)." It goes on a function declaration, takes a boolean parameter, and causes a behavioral change in the declared function. A requires-expression says, "Compiler, please tell me whether (some set of expressions) is well-formed." It is itself a boolean expression. It has no "side effects" on the behavior of the program — it's just asking the compiler for the answer to a yes/no question. "Is this expression well-formed?"

我们可以在require -子句中嵌套require -表达式,但我们通常认为这样做是糟糕的风格。

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

将需求表达式封装在类型-trait中被认为是更好的风格…

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

我发现Andrew Sutton (Concepts的作者之一,在gcc中实现了它)的评论在这方面很有帮助,所以我想在这里引用它的几乎全部:

Not so long ago requires-expressions (the phrase introduced by the second requires) was not allowed in constraint-expressions (the phrase introduced by the first requires). It could only appear in concept definitions. In fact, this is exactly what is proposed in the section of that paper where that claim appears. However, in 2016, there was a proposal to relax that restriction [Editor's note: P0266]. Note the strikethrough of paragraph 4 in section 4 of the paper. And thus was born requires requires. To tell the truth, I had never actually implemented that restriction in GCC, so it had always been possible. I think that Walter may have discovered that and found it useful, leading to that paper. Lest anybody think that I wasn't sensitive to writing requires twice, I did spend some time trying to determine if that could be simplified. Short answer: no. The problem is that there are two grammatical constructs that need to introduced after a template parameter list: very commonly a constraint expression (like P && Q) and occasionally syntactic requirements (like requires (T a) { ... }). That's called a requires-expression. The first requires introduces the constraint. The second requires introduces the requires-expression. That's just the way the grammar composes. I don't find it confusing at all. I tried, at one point, to collapse these to a single requires. Unfortunately, that leads to some seriously difficult parsing problems. You can't easily tell, for example if a ( after the requires denotes a nested subexpression or a parameter-list. I don't believe that there is a perfect disambiguation of those syntaxes (see the rationale for uniform initialization syntax; this problem is there too). So you make a choice: make requires introduce an expression (as it does now) or make it introduce a parameterized list of requirements. I chose the current approach because most of the time (as in nearly 100% of the time), I want something other than a requires-expression. And in the exceedingly rare case I did want a requires-expression for ad hoc constraints, I really don't mind writing the word twice. It's a an obvious indicator that I haven't developed a sufficiently sound abstraction for the template. (Because if I had, it would have a name.) I could have chosen to make the requires introduce a requires-expression. That's actually worse, because practically all of your constraints would start to look like this: template<typename T> requires { requires Eq<T>; } void f(T a, T b); Here, the 2nd requires is called a nested-requirement; it evaluates its expression (other code in the block of the requires-expression is not evaluated). I think this is way worse than the status quo. Now, you get to write requires twice everywhere. I could also have used more keywords. This is a problem in its own right---and it's not just bike shedding. There might be a way to "redistribute" keywords to avoid the duplication, but I haven't given that serious thought. But that doesn't really change the essence of the problem.