Static Constexpr' Function Called in a Constant Expression Is...An Error

`static constexpr` function called in a constant expression is...an error?

As T.C. demonstrated with some links in a comment, the standard is not quite clear on this; a similar problem arises with trailing return types using decltype(memberfunction()).

The central problem is that class members are generally not considered to be declared until after the class in which they're declared is complete. Thus, regardless of the fact that foo is static constexpr and its declaration precedes that of bar, it cannot be considered "available" for use in a constant expression until MyClass is complete.

As pointed out by Shafik Yaghmour, there is some attempt within the standard to avoid a dependency on the ordering of members within a class, and obviously allowing the example in the original question to compile would introduce an ordering dependency (since foo would need to be declared before bar). However, there is already a minor dependency on ordering, because although constexpr functions can't be called inside noexcept, a noexcept expression itself might depend on an earlier declaration inside the class:

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

(Note that this is not actually a violation of 3.3.7, since there is still only one correct program that is possible here.)

This behavior may actually be a violation of the standard; T.C. points out (in a comment below) that foo here should actually be looked up in the scope of the whole class. Both g++ 4.9.2 and clang++ 3.5.1 fail with an error when bar is declared first but compile with no errors or warnings when foo is declared first. EDIT: clang++ trunk-revision 238946 (from shortly before the release of 3.7.0) does not fail when bar is declared first; g++ 5.1 still fails.

Intriguingly, the following variation causes a "different exception specifier" with clang++ but not with 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())) { }

According to the error, the noexcept specification for the declaration of bar2 evaluates to noexcept(false), which is then considered a mismatch for noexcept(noexcept(MyClasss::foo2())).

What determines whether a constexpr function is a constant expression?

Both programs are "ill-formed no diagnostic required", per [dcl.constexpr]/6:

For a constexpr function or constexpr constructor that is neither defaulted nor a template, if no argument values exist such that an invocation of the function or constructor could be an evaluated subexpression of a core constant expression, or, for a constructor, an evaluated subexpression of the initialization full-expression of some constant-initialized object ([basic.start.static]), the program is ill-formed, no diagnostic required.

It's a bit strange that gcc just fails to notice the issue with the second program, but it's still conforming.

Note a diagnostic would be required if f were used in a context that actually requires a constant expression, for example constexpr int n = f();.

Some things are never permitted in a constexpr function. These do require a diagnostic (typically an error message), even if the function is never used in a constant expression - see cigien's answer. But the programs in the question don't violate any of these stricter rules.

constexpr not working if the function is declared inside class scope

Yes, it is ill-formed. Here's why:

A constexpr function needs to be defined (not just declared) before being used in a constant expression.

So for example:

constexpr int f(); // declare f
constexpr int x = f(); // use f - ILLEGAL, f not defined
constexpr int f() { return 5; } // define f, too late

function definitions inside a class specifier (as well as initializers and default parameters) are essentially parsed in an order like they were defined outside the class.

So this:

struct X {
constexpr static int size() { return 5; }
static const int array[size()];
};

Is parsed in this order:

struct X {
constexpr inline static int size(); // function body defered
static const int array[size()]; // <--- POINT A
};

constexpr inline int X::size() { return 5; }

That is, parsing of function bodies are defered until after the class specifier.

The purpose of this deferral of function body parsing is so that function bodies can forward reference class members not yet declared at that point, and also so they can use their own class as a complete type:

struct X
{
void f() { T t; /* OK */ }
typedef int T;
};

Compared to at namespace scope:

void f() { T t; /* error, T not declared */ }
typedef int T;

At POINT A, the compiler doesn't have the definition of size() yet, so it can't call it. For compile-time performance constexpr functions need to be defined ahead of their use in the translation unit before being called during compile, otherwise the compiler would have to make a multiple passes just to "link" constant expressions for evaluation.

Why is this constexpr-function giving me errors?

The sample code only runs with C++17 or higher.

It seems that you are using Visual Studio. And if so, you can go to the Project Protities and do the following change to make your code run properly.

2021-06-20_00.png

Why can't I use the result of a static constexpr in the declaration of another function in a class?

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}; }
};

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';
}

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';
}

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; }
};

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;
};
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;
};

Function is not usable as a 'constexpr' function

In order to evaluate this:

  if constexpr (p(x)) {

We need for p(x) to be a constant expression. The rules for whether something qualifies as a constant expression or not are based on a list of things that you're not allowed to do.

When x is a basic_type<int> and p is a function that takes a basic_type<int> by value, there are simply no rules that we are violating. This is an empty type, so copying it (as we're doing here) doesn't actually involve any kind of read. This just works.


But when x is an int and p is a function that takes an int by value, this also requires copying x but this time it involves reading the value of x. Because, of course, gotta initialize the parameter somehow. And this does run afoul of a rule: [expr.const]/8 says we're not allowed to perform:

an lvalue-to-rvalue conversion unless it is applied to

  • a non-volatile glvalue that refers to an object that is usable in constant expressions, or
  • a non-volatile glvalue of literal type that refers to a non-volatile object whose lifetime began within the evaluation of E;

An lvalue-to-rvalue conversion is what happens when we read the value of a variable, and neither of those bullets apply. It doesn't matter here that you don't actually care what the value is, since p doesn't use it. In order to be able to even invoke p, you have to copy x, and you're not allowed to do that. Hence the error.


However, your lambda here doesn't actually need the value, just the type, so you could instead write this:

    return foo(
[]<typename T>(T const&)
{
return std::is_integral_v<std::decay_t<T>>;
},
0);

Now we're no longer copying x into the lambda, since the lambda no longer takes by value - it takes by reference. As a result, we're not violating the lvalue-to-rvalue conversion rule (or any other rule) and this is now a valid constant expression.


Then, as a bonus, if you change foo to take x by reference (because, again, you don't actually care about the value, so why not):

consteval auto foo(auto p, auto const& x) noexcept {
if constexpr (p(x)) {
return 1;
} else {
return 0;
}
}

Then both variants become ill-formed. Both the basic_type<int> and int versions (regardless of whether you take the int by value or by reference). For more on this case, see the constexpr array size problem which I'm currently trying to resolve with P2280.

C++11 - Can't define constexpr literal using constexpr function?

In C++, inline definitions of member functions for a class are only parsed after the declaration of the class is complete.

So even though the compiler "knows" about MyClass::FooValue(int), it hasn't "seen" its definition yet, and hence it can't be used in a constexpr expression.

A general workaround for this is to stick to constexpr member functions, or declare constexpr constants outside the class.



Related Topics



Leave a reply



Submit