Why Is This Constexpr Static Member Function Not Seen as Constexpr When Called

Why is this constexpr static member function not seen as constexpr when called?

From memory, member function bodies are evaluated only once the class has been completely defined.

static constexpr int bah = static_n_items(); 

forms part of the class definition, but it's referring to a (static) member function, which cannot yet be defined.

Solution:

defer constant expressions to a base class and derive from it.

e.g.:

struct Item_id_base
{
enum Enum
{
size, position, attributes, window_rect, max_window_size, _
};

static constexpr int n_items_ = _; // OK
constexpr auto member_n_items() const -> int { return _; } // OK
static constexpr auto static_n_items() -> int { return _; } // OK
static constexpr int so_far = n_items_; // OK
};

struct Item_id : Item_id_base
{
#ifndef OUT_OF_CLASS
static constexpr int bah = static_n_items(); // now OK
#endif
};

constexpr auto n_ids() -> int { return Item_id().member_n_items(); } // OK

auto main() -> int
{
#ifdef OUT_OF_CLASS
static constexpr int bah = Item_id::static_n_items(); // OK
#endif
}

Why do you think the standard disallows it?

Because this is illegal:

struct Item_id
{
// ... etc.

#ifndef OUT_OF_CLASS
static constexpr int bah;// = static_n_items(); //! Nah.
#endif
};

constexpr int Item_id::bah = static_n_items();

And a constexpr must have a constexpr definition. The only place we can define it is during its declaration...

... so by deduction it cannot refer to any function who's body is not yet defined.

I am at a loss to know where to look in the standard for all that. Probably 5 different, seemingly unrelated clauses :)

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

Why can't a static constexpr member variable be passed to a function?

The problem was neither with the code itself, nor with the standard being used. CLion's default compiler does not fully support C++17, so that's why it showed a strange behavior that it could compile static constexpr member variables, but only as long as they were not passed to functions.

After updating to the most recent compiler version, I was able to run the code successfully without any changes.

Thank you for all your contribution.

Constexpr static member function usage

Looks like a bug to me.

The type and meaning of the expression h.size() is defined by [expr.ref] "Class member access":

[expr.post]/3


Abbreviating postfix-expression.id-expression as E1.E2, E1 is called the object expression. [...]

and

[expr.post]/6.3.1


If E2 is a (possibly overloaded) member function, function overload resolution is used to determine whether E1.E2 refers to a static or a non-static member function.

  • (6.3.1) If it refers to a static member function and the type of E2 is “function of parameter-type-list returning T”, then E1.E2 is an lvalue; the expression designates the static member function. The type of E1.E2 is the same type as that of E2, namely “function of parameter-type-list returning T”.

This means h.size has the same type as ::MyClass::size and is evaluated as such, regardless of the fact that h is constexpr or not.

h.size() is then a call to a constexpr function and is a core constant expression according to [expr.const]/4.

`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())).

Constexpr member function

The code is fine as written.

[dcl.constexpr]

6 If the instantiated template specialization of a constexpr
function template or member function of a class template would fail to
satisfy the requirements for a constexpr function or constexpr
constructor, that specialization is still a constexpr function or
constexpr constructor, even though a call to such a function cannot
appear in a constant expression. If no specialization of the template
would satisfy the requirements for a constexpr function or constexpr
constructor when considered as a non-template function or constructor,
the template is ill-formed, no diagnostic required.

The member may not appear in a constant expression for the specialization that uses Dynamic_engine, but as the paragraph above details, that does not make S::size ill-formed. We are also far from ill-formed NDR territory, since valid instantations are possible. Static_engine being a prime example.

The quote is from n4659, the last C++17 standard draft, and similar wording appears in the latest C++20 draft.


As for the evaluation of sta.size() as a constant expression, going over the list at [expr.const] I cannot find anything that is disallowed in the evaluation itself. It is therefore a valid constant expression (because the list tells us what isn't valid). And in general for a constexpr function to be valid, there just needs to exist some set of arguments for which the evaluation produces a valid constant expression. As the following example form the standard illustrates:

constexpr int f(bool b)
{ return b ? throw 0 : 0; } // OK
constexpr int f() { return f(true); } // ill-formed, no diagnostic required

struct B {
constexpr B(int x) : i(0) { } // x is unused
int i;
};

int global;

struct D : B {
constexpr D() : B(global) { } // ill-formed, no diagnostic required
// lvalue-to-rvalue conversion on non-constant global
};

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.



Related Topics



Leave a reply



Submit