为什么不能在类的另一个函数的声明中使用静态constexpr的结果?

ksh*_*noy 4 c++ c++11

这是我的准系统代码:

#include <iostream>
#include <array>

class cColor {
  public:
  enum eValue { k_Red, k_Green, k_Blue };
  static constexpr std::size_t NumValues() { return 3; }
  static constexpr std::array<eValue, NumValues()> Values()  { return {k_Red, k_Green, k_Blue}; }
};

int main() {
  std::cout << "NumColors=" << cColor::NumValues() << '\n';
}
Run Code Online (Sandbox Code Playgroud)

我试图声明Values()为静态constexpr,我认为我也应该能够使用,NumValues()因为它也是静态constexpr。但是,该程序无法编译,并引发以下错误:

main.cpp:8:39: error: non-type template argument is not a constant expression
  static constexpr std::array<eValue, NumValues()> Values()  { return {k_Red, k_Green, k_Blue}; }
                                      ^~~~~~~~~~~
main.cpp:8:39: note: undefined function 'NumValues' cannot be used in a constant expression
main.cpp:7:32: note: declared here
  static constexpr std::size_t NumValues() { return 3; }
Run Code Online (Sandbox Code Playgroud)

但是,如果我使用静态constexpr成员变量,它就可以正常工作

#include <iostream>
#include <array>

class cColor {
  public:
  enum eValue { k_Red, k_Green, k_Blue };
  static constexpr std::size_t NumValues {3};
  static constexpr std::array<eValue, NumValues> Values()  { return {k_Red, k_Green, k_Blue}; }
};

int main() {
  std::cout << "NumColors=" << cColor::NumValues << '\n';
}
Run Code Online (Sandbox Code Playgroud)

那么,阻止代码编译的静态constexpr成员函数又是什么呢?

Cru*_*ean 5

It's because it's in a class definition. You can't use the static functions of a class at compile time until after the full definition of the class.

If the reason is unclear, your code is actually this:

class cColor {
  public:
  enum eValue { k_Red, k_Green, k_Blue };
  static constexpr std::size_t NumValues() { return 3; }
  static constexpr std::array<cColor::eValue, cColor::NumValues()> Values()  { return {k_Red, k_Green, k_Blue}; }
};
Run Code Online (Sandbox Code Playgroud)

You see, when you say std::array<cColor::eValue, cColor::NumValues()> as the return type, you're using cColor::NumValues(), which uses cColor which has not yet been defined (because it's inside the class definition.

You're effectively defining a component of cColor in terms of itself.

The problem is solved by moving the self-referential component outside of the class (one or both of them):

#include <iostream>
#include <array>

static constexpr std::size_t NumValues() { return 3; }

class cColor {
  public:
  enum eValue { k_Red, k_Green, k_Blue };

  static constexpr std::array<eValue, NumValues()> Values()  { return {k_Red, k_Green, k_Blue}; }
};

int main() {
  std::cout << "NumColors=" << NumValues() << '\n';
}
Run Code Online (Sandbox Code Playgroud)

Edit:

To further answer your question as to why specifically the use of a constexpr function is causing problems (as opposed to your revised question using a constexpr variable), I'll give this next piece of information:

Have you ever noticed that you can't use a function before it's declared, but you can use a member function before it's declared (in the class definition I mean).

That's because the C++ compiler glosses over the entire class, including the member/static variables and methods/static methods before it does anything else. Therefore the methods/static methods (which I'll collectively refer to as member functions) must have well-formed declarations prior to having any of their actual implementation defined - this of course includes their return types.

Thus, at compile time, when the declaration for Values() is examined, it knows the return type depends on NumValues(), and it knows NumValues() returns std::size_t, but it hasn't yet examined the implementation of any member functions in the class yet. Thus it doesn't know (yet) that NumValues() will return 3.

Thus, you can also solve this problem by using delayed return type deduction. The real crux of the problem is that Values() must have a well-formed return type before the implementation of its class's member functions are inspected.

Here's another solution that might illuminate the specifics of the problem:

#include <iostream>
#include <array>

class cColor {
  public:
  enum eValue { k_Red, k_Green, k_Blue };
  static constexpr std::size_t NumValues() { return 3; }
  static constexpr auto Values() { return std::array<eValue, NumValues()>{k_Red, k_Green, k_Blue}; }
};

int main() {
  std::cout << "NumColors=" << cColor::NumValues() << '\n';
}
Run Code Online (Sandbox Code Playgroud)

You see, auto is a valid return type for the signature, and the actual return type is deduced from the method implementation, which at that point knows the implementation of NumValues().

The reason for this weird compiler parsing order is so that you don't have to arrange methods in a certain order for them to compile (under normal circumstances - read on). This way all methods are known prior to any implementations, which is sort of like having a forward declaration for every method in the class.

And just if you were wondering, yes, moving the definition/declaration of NumValues() to be after Values() will cause a compilation failure because our trick doesn't work anymore since the implementation for NumValues() is examined after the implementation for Values() and thus Values() doesn't know NumValues() returns 3:

class cColor {
  public:
  enum eValue { k_Red, k_Green, k_Blue };

  // THIS ORDERING FAILS TO COMPILE
  static constexpr auto Values() { return std::array<eValue, NumValues()>{k_Red, k_Green, k_Blue}; }
  static constexpr std::size_t NumValues() { return 3; }
};
Run Code Online (Sandbox Code Playgroud)

Your example works because constexpr variables must be defined and declared at the same time, so the value of 3 is known at that point onward and thus makes the declarations valid. However, if you move the declaration/definition of the static constexpr member variable to be after Values() you again have a compile error which can be fixed by using the auto hack I demonstrated above.

class cColor {
  public:
  enum eValue { k_Red, k_Green, k_Blue };

  // THIS ORDERING FAILS TO COMPILE
  static constexpr std::array<eValue, NumValues> Values() { return {k_Red, k_Green, k_Blue}; }
  static constexpr std::size_t NumValues = 3;
};
Run Code Online (Sandbox Code Playgroud)
class cColor {
  public:
  enum eValue { k_Red, k_Green, k_Blue };

  // AUTO TRICK MAKES THIS WORK
  static constexpr auto Values() { return std::array<eValue, NumValues>{k_Red, k_Green, k_Blue}; }
  static constexpr std::size_t NumValues = 3;
};
Run Code Online (Sandbox Code Playgroud)