在常量表达式中调用的`static constexpr`函数是...一个错误吗?

41

我有以下的代码:

class MyClass
{
  static constexpr bool foo() { return true; }
  void bar() noexcept(foo()) { }    
};

我原以为由于foo()是一个static constexpr函数,并且它在bar被声明之前被定义,所以这应该是完全可以接受的。

然而,g++却给我以下错误:

 error: ‘static constexpr bool MyClass::foo()’ called in a constant expression

这并没有什么帮助,因为在常量表达式中调用函数的能力是constexpr的全部意义所在。

clang++则更加有用,除了一个错误信息,指出noexcept的参数必须是常量表达式,它还显示:

note: undefined function 'foo' cannot be used in a constant expression
note: declared here
static constexpr bool foo() { return true; }
                      ^

所以...这是一个需要两次编译的问题吗?是编译器试图在定义任何成员函数之前声明类中的所有成员函数所引起的问题吗?(注意,在类的上下文之外,没有编译器会报错。)这让我很惊讶;直觉上,我不认为static constexpr成员函数不能在类内外的所有常量表达式中使用。


4
链接1: http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_active.html#1255链接2: http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_active.html#1626这两个链接都是指向C++编程语言国际标准委员会(WG21)网站上的问题列表页面,其中包含有关C++语言的技术问题和解决方案。 - T.C.
1
@MattMcNabb 但是在你的例子中,在使用它的noexcept表达式之前,bar的主体并不完整,因此没有不一致性。 - Kyle Strand
1
@T.C. 我对你能找到这些宝石感到困惑。你应该写一个答案,因为这非常不明显。 - vsoftco
1
T.C.的链接指出,标准规定在类声明完成(})之前不能引用constexpr。 - brian beuning
1
@KyleStrand 或许我的例子不太好,但目前函数定义的顺序和在类中声明的顺序没有依赖关系。您的提议将引入这种依赖性,可能会带来一些麻烦。CWG 1255 中的 auto 示例说明了其中的一个问题。 - M.M
显示剩余8条评论
1个回答

23

正如T.C.在评论中提供的一些链接所示,标准并不完全明确;使用decltype(memberfunction())的尾随返回类型也会出现类似的问题。

核心问题在于,类成员通常在包含它们的类完成声明之后才被认为已经声明。因此,无论foostatic constexpr还是它的声明在bar之前,它在常量表达式中都不能被视为“可用”,直到MyClass完成声明。

正如Shafik Yaghmour指出的那样,标准内部有些尝试避免对类成员顺序的依赖,显然,如果允许原始问题中的示例编译通过,则会引入一个顺序依赖(因为foo需要在bar之前声明)。然而,由于constexpr函数无法在noexcept中调用,所以noexcept表达式本身可能取决于类内先前的声明,因此已经存在一个轻微的顺序依赖:

class MyClass
{
    // void bar() noexcept(noexcept(foo())); // ERROR if declared here
    static constexpr bool foo();
    void bar() noexcept(noexcept(foo())); // NO ERROR
}

(请注意,这实际上并不违反3.3.7,因为在这里仍然只有一个正确的程序可能。)
这种行为实际上可能违反了标准;T.C.指出(在下面的评论中)foo 实际上应该在整个类的范围内查找。当 bar 被先声明时,g++ 4.9.2 和 clang++ 3.5.1 都会失败并报错,但当 foo 被先声明时,则编译没有错误或警告。编辑:clang++ trunk-revision 238946(在3.7.0发布之前不久)在 bar 被先声明时 失败;而 g++ 5.1 仍然失败。 有趣的是,以下变化在 clang++ 中导致“异常说明符不同”的情况,但在 g++ 中则 没有
class MyClass
{
  static constexpr bool foo2();
  void bar2() noexcept(noexcept(foo2()));
};

constexpr bool MyClass::foo2() { return true; }
void MyClass::bar2() noexcept(noexcept(MyClass::foo2())) { }

根据错误提示,对于函数 bar2声明中的 noexcept 规范被评估为 noexcept(false),这被认为与noexcept(noexcept(MyClasss::foo2()))不匹配。

1
有点令人惊讶。在异常规范中的名称应该在整个类的作用域内查找([basic.lookup.unqual]/p8,[basic.scope.class]/p1)。我想知道这是否只是编译器不符合标准的问题。 - T.C.
@T.C. ....嗯。你是说上面的代码片段应该在声明顺序无论如何都有效吗?那不需要某种两遍声明方案吗? - Kyle Strand
你已经通过成员函数体和NSDMI拥有了这个功能;这条规则并不是全新的。 - T.C.
@T.C. 我不确定我立刻理解那里的含义。你能举个例子吗? - Kyle Strand
6
标准规定,你可以这样使用struct C { int *x = &y; int y; };以及struct C { int x() { return y; } int y; };。在这两种情况下,允许在声明之前使用类成员,因此显然需要推迟至少一部分成员的解析工作直到类完成。 - T.C.
显示剩余5条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接