constexpr static data member without initializer

2020-06-01 05:43发布

问题:

#include <complex>

struct S
{
  static std::complex<double> constexpr c;
};

gcc generates an error because an initializer is missing. Clang and MSVC do not generate an error.

As far as I know a constexpr static data member must have an initializer, even if it is of class type that has a constructor that can be called without arguments (as in this case). Unfortunately I don't have the latest C++ standard to back up my assumption.

So the correct code should initialize with a constructor, for instance:

struct S
{
  static std::complex<double> constexpr c {};
};

Can anyone prove which compiler is right and which is wrong?

回答1:

GCC is wrong.

GCC uses the C++14 rules for constexpr variables, which requires an initializer to be provided. This is changed per P0386 (bold text is newly added text):

In 9.2.3.2p3, change:

If a non­-volatile n​on-­inline ​const static data member is of integral or enumeration type, its declaration in the class definition can specify a b​race­-or-­equal-­initializer​ in which every initializer-­clause​ that is an ​assignment-­expression​ is a constant expression (5.20). A​ static data member of literal type can be declared in the class definition with the constexpr specifier; if so, its declaration shall specify a brace­-or-­equal-initializer in which every initializer-­clause that is an assignment­-expression is a constant expression. [ Note: In both these cases, the member may appear in constant expressions. — end note ]​ The member shall still be defined in a namespace scope if it is odr-­used (3.2) in the program and the namespace scope definition shall not contain an initializer.​ A​n inline static data member can be defined in the class definition and may specify a b​race­-or-­equal­-initializer.​ If the member is declared with the constexpr specifier, it may be redeclared in namespace scope with no initializer (this usage is deprecated; see D.X). Declarations of other static data members shall not specify a b​race­-or-­equal-initializer.​



回答2:

In this particular case, there are two answers:

  • For C++14, gcc is right (i.e. constexpr static data member must have an initializer).
  • For C++17 and beyond, gcc is wrong, since it refuses to compile conformant code.

Former case: In draft N3797 (C++14), 9.4.2.3 (Static data members) [class.static.data] (emphasis mine):

A static data member of literal type can be declared in the class definition with the constexpr specifier; if so, its declaration shall specify a brace-or-equal-initializer in which every initializer-clause that is an assignment-expression is a constant expression.

See also: http://en.cppreference.com/w/cpp/language/static#Constant_static_members.

I said "in this particular case", because std::complex has a specialization for double that is a LiteralType. Therefore above rule applies. For general (i.e. non-literal) types, see codekaizers answer.

Latter case: For C++17, see xskxzr's answer.



回答3:

From dcl.constexpr#1:

A function or static data member declared with the constexpr specifier is implicitly an inline function or variable

constexpr static data members are implicitly inline.

Also from class#static.data-3, emphasis mine:

inline static data member may be defined in the class definition and may specify a brace-or-equal-initializer.


Thus, GCC is wrong. brace-or-equal-initializer is not strictly required.

Reference: N4659 C++17 Draft