C++ constexpr 类型为嵌套类

mem*_*tha 7 c++ constexpr clang++ c++20

这有效:(A)

class Foo {
public:
  const bool b;
  constexpr ~Foo() = default;
  constexpr Foo(const bool b) : b(b) {};
};

class Bar {
public:
  static constexpr Foo tru { true };//Foo is complete type
};
Run Code Online (Sandbox Code Playgroud)

编译失败:(B)

class Bar {
public:
  class Foo {
  public:
    const bool b;
    constexpr ~Foo() = default;
    constexpr Foo(const bool b) : b(b) {};
  };
  static constexpr Foo tru { true };//undefined constructor 'Foo' cannot be used
};
Run Code Online (Sandbox Code Playgroud)

错误:

$ clang++ --std=c++20 -D_POSIX_C_SOURCE=200112L -fPIC -g -Werror -Wall LiteralStruct.cpp -o LiteralStruct
LiteralStruct.cpp:9:24: error: constexpr variable 'tru' must be initialized by a constant expression
  static constexpr Foo tru { true };
                       ^~~~~~~~~~~~
LiteralStruct.cpp:9:24: note: undefined constructor 'Foo' cannot be used in a constant expression
LiteralStruct.cpp:7:15: note: declared here
    constexpr Foo(const bool b) : b(b) {};
              ^
1 error generated.
Run Code Online (Sandbox Code Playgroud)

这也无法编译,但给出了一个很好的理由:(C)

$ clang++ --std=c++20 -D_POSIX_C_SOURCE=200112L -fPIC -g -Werror -Wall LiteralStruct.cpp -o LiteralStruct
LiteralStruct.cpp:9:24: error: constexpr variable 'tru' must be initialized by a constant expression
  static constexpr Foo tru { true };
                       ^~~~~~~~~~~~
LiteralStruct.cpp:9:24: note: undefined constructor 'Foo' cannot be used in a constant expression
LiteralStruct.cpp:7:15: note: declared here
    constexpr Foo(const bool b) : b(b) {};
              ^
1 error generated.
Run Code Online (Sandbox Code Playgroud)

错误:

$ clang++ --std=c++20 -D_POSIX_C_SOURCE=200112L -fPIC -g -Werror -Wall LiteralStruct.cpp -o LiteralStruct
LiteralStruct.cpp:6:24: error: constexpr variable cannot have non-literal type 'const Foo'
  static constexpr Foo tru { true };
                       ^
LiteralStruct.cpp:6:24: note: incomplete type 'const Foo' is not a literal type
LiteralStruct.cpp:1:7: note: definition of 'Foo' is not complete until the closing '}'
class Foo {
Run Code Online (Sandbox Code Playgroud)

版本:

clang version 10.0.0-4ubuntu1 
Target: x86_64-pc-linux-gnu
Run Code Online (Sandbox Code Playgroud)

C 失败是有道理的,并且有一个很好的错误消息。B 感觉它应该可以工作,Foo 及其所有内容都应该完整并在文件中的该点进行定义。基本上我的问题是:我是报告 B 应该起作用的 clang bug,还是要求更好的错误消息的功能请求?如果 Foo 由于是不完整类型的成员而确实不完整,那么我认为错误消息应该类似于 C 的错误消息。

编辑:

我刚刚将 clang 升级到了前沿 ( 16.0.0-++20221021052626+7dd2f4bc009d-1~exp1~20221021172738.418) 并得到了相同的结果。

use*_*522 3

(B) 的问题与(C) 的问题不同。在(B) 中, 的完整性Foo不存在问题。Foo一旦}达到其定义的结束,就完成了,并且由于 的​​成员声明tru放置此之后,Foo因此就完成了其目的。

(B) 中也不存在Bar声明不完整的问题tru。虽然这是事实,但不完整性并不妨碍查找先前声明的成员,例如嵌套类Foo

问题是,正如错误消息指出的那样,构造函数是否Foo::Foo(bool)在 的声明点定义tru。这是一个重要的问题,因为tru是使用相关构造函数在该声明中初始化的,并且它被标记为constexpr,要求初始化是一个常量表达式。仅当函数在表达式之前定义(不仅仅是声明)时,才允许在常量表达式中调用 (constexpr) 函数。

例如考虑

class Bar {
public:
  class Foo {
  public:
    const bool b;
    constexpr ~Foo() = default;
    constexpr Foo(const bool b);
  };
  static constexpr Foo tru { true };
};

constexpr Bar::Foo::Foo(const bool b) : b(b) {};
Run Code Online (Sandbox Code Playgroud)

您将在此处收到相同或类似的错误消息。在这种情况下,问题所在就更明显了。当static constexpr Foo tru { true };达到 且编译器尝试计算 的(编译时常量)值时Foo,它还没有看到构造函数的定义,因此它不知道如何确定 的值tru

现在,在您的示例(B)中,似乎在常量表达式 for 中使用之前Bar::Foo::Foo(bool)定义的,我认为如果通过精确的措辞遵循当前标准,那么这是正确的。然而,在实践中和标准的可能意图中,有一个复杂的情况改变了这一点:tru

类内部定义的函数体很特殊,因为它是所谓的完整类上下文。在这样的上下文中,名称查找不仅可以查找前面的声明(如 C++ 中通常的情况),还可以查找类(和封闭类)的所有成员的声明,无论它们是否仅在稍后声明。

例如,以下内容是允许的:

class Bar {
public:
  class Foo {
  public:
    const bool b;
    ~Foo() = default;
    Foo(const bool b) : b(X) {};
  };
  constexpr static bool X = false;
};
Run Code Online (Sandbox Code Playgroud)

尽管在定义和使用X时尚未声明,但编译器必须接受它并找出最后声明的静态成员。Foo::Foo(bool)XX

为了实现这种查找行为,编译器实际上必须将代码重写为

class Bar {
public:
  class Foo {
  public:
    const bool b;
    ~Foo() = default;
    Foo(const bool b);
  };
  constexpr static bool X = false;
};

inline Bar::Foo(const bool b) : b(X) {};
Run Code Online (Sandbox Code Playgroud)

现在“正常”查找规则可以X按预期找到。

但是,如果我们将此重写应用于您的示例 (B),我们会在该答案中得到我的第一个示例,并且我们确定它不能与常量表达式求值一起使用。因此,在实践中,您不能在类定义本身内的静态数据成员的常量表达式求值中使用同一类(包括嵌套或封闭类)的成员函数。

当前的标准措辞没有正确描述这种行为是标准的问题,而不是编译器的问题。我的印象是 Clang 正在按预期实现它。虽然有时可以考虑为了常量表达式求值而在词法上放置的构造函数,例如,如果此时已经可以找到其定义中使用的所有名称,但通常这是不可能的,因为您可以创建无限以这种方式递归类型依赖。

参见例如有效的CWG 问题1255CWG 问题1626