Constexpr Initializing Static Member Using Static Function

constexpr initializing static member using static function

The Standard requires (section 9.4.2):

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.

In your "second attempt" and the code in Ilya's answer, the declaration doesn't have a brace-or-equal-initializer.

Your first code is correct. It's unfortunate that gcc 4.6 isn't accepting it, and I don't know anywhere to conveniently try 4.7.x (e.g. ideone.com is still stuck on gcc 4.5).

This isn't possible, because unfortunately the Standard precludes initializing a static constexpr data member in any context where the class is complete. The special rule for brace-or-equal-initializers in 9.2p2 only applies to non-static data members, but this one is static.

The most likely reason for this is that constexpr variables have to be available as compile-time constant expressions from inside the bodies of member functions, so the variable initializers are completely defined before the function bodies -- which means the function is still incomplete (undefined) in the context of the initializer, and then this rule kicks in, making the expression not be a constant expression:

an invocation of an undefined constexpr function or an undefined constexpr constructor outside the definition of a constexpr function or a constexpr constructor;

Consider:

class C1
{
constexpr static int foo(int x) { return x + bar; }
constexpr static int bar = foo(sizeof(int));
};

Can I initialize a `constexpr static` member outside the class?

constexpr goes on the initializing declaration of a variable, so just put it outside the class:

struct Header
{
int msgType = -1, len;

static const std::size_t MAX_SIZE;

Header() { len = sizeof(*this); }
};

// ... declaration of subclasses ...

inline constexpr std::size_t Header::MAX_SIZE = std::max({ sizeof(A), sizeof(B), sizeof(C) });

Note that the implicit const must be spelled out in the declaration. The definition should go in the same header to avoid any translation unit seeing the declaration but not the inline, which is not allowed.

Initialize static constexpr member variable of class template

You can specialize the class template as

template <int N>
struct Foo {
static constexpr float val { 0.0f };
};
template <>
struct Foo<0> {
static constexpr float val { 3.14f };
};
template <>
struct Foo<1> {
static constexpr float val { 0.1f };
};

Or make a function helper for initialization.

template <int N>
struct Foo {
static constexpr float get_val() {
if constexpr (N == 0) return 3.14f;
else if constexpr (N == 1) return 0.1f;
else return 0.0f;
}
static constexpr float val { get_val() };
};

Initializing a static constexpr data member of the base class by using a static constexpr data member of the derived class

For C++14 and 11, Clang is right; however, things have changed in the latest working draft (the future C++17) - see the next section.

The Standard quotes to look for are (from N4140, the draft closest to C++14):

[temp.inst]/1:

[...] The implicit instantiation of a class template specialization
causes the implicit instantiation of the declarations, but not of the
definitions, default arguments, or exception-specifications of the
class member functions, member classes, scoped member enumerations,
static data members and member templates; [...]

[temp.point]/4:

For a class template specialization, [...] the point of instantiation
for such a specialization immediately precedes the namespace scope
declaration or definition that refers to the specialization.

So, the point of instantiation for S<U> is right before the declaration of U, with only a forward declaration struct U; conceptually inserted before, so that the name U is found.

[class.static.data]/3:

[...] 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. [...] 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.

According to the paragraph quoted above, the declaration of bar within the definition of S, even though it has an initializer, is still just a declaration, not a definition, so it's instantiated when S<U> is implicitly instantiated, and there's no U::foo at that time.

A workaround is to make bar a function; according to the first quote, the function's definition will not be instantiated at the time of the implicit instantiation of S<U>. As long as you use bar after the definition of U has been seen (or from within the bodies of other member functions of S, since those, in turn, will only be instantiated separately when needed - [14.6.4.1p1]), something like this will work:

template<class T> struct S 
{
static constexpr int bar() { return T::foo; }
};

struct U : S<U> { static constexpr int foo = 42; };

int main()
{
constexpr int b = U::bar();
static_assert(b == 42, "oops");
}

Following the adoption of P0386R2 into the working draft (currently N4606), [class.static.data]/3 has been amended; the relevant part now reads:

[...] An inline static data member may be defined in the class definition
and may specify a brace-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.1). [...]

This is complemented by the change to [basic.def]/2.3:

A declaration is a definition unless:

[...]

  • it declares a non-inline static data member in a class definition (9.2, 9.2.3),

[...]

So, if it's inline, it's a definition (with or without an initializer). And [dcl.constexpr]/1 says:

[...] A function or static data member declared with the constexpr
specifier is implicitly an inline function or variable (7.1.6). [...]

Which means the declaration of bar is now a definition, and according to the quotes in the previous section it's not instantiated for the implicit instantiation of S<U>; only a declaration of bar, which doesn't include the initializer, is instantiated at that time.

The changes in this case are nicely summarized in the example in [depr.static_constexpr] in the current working draft:

struct A {
static constexpr int n = 5; // definition (declaration in C++ 2014)
};

const int A::n; // redundant declaration (definition in C++ 2014)

This makes GCC's behaviour standard-conformant in C++1z mode.

Static member access in constant expressions

Clang seems to be in the right. When accessing a static member with the member access syntax [class.static/1]:

A static member s of class X may be referred to using the qualified-id
expression X​::​s; it is not necessary to use the class member access
syntax to refer to a static member. A static member may be referred to
using the class member access syntax, in which case the object
expression is evaluated.

So s.v() will cause s to be evaluated. Now, according to [expr.const/2.11], s is not a constant expression:

2 An expression e is a core constant expression unless the evaluation
of e, following the rules of the abstract machine, would evaluate one
of the following expressions:

[...]

an id-expression that refers to a variable or data member of reference
type unless the reference has a preceding initialization and either:

(2.11.1) - it is initialized with a constant expression or

(2.11.2) - its lifetime began within the evaluation of e;

s doesn't have a preceding initialization with a constant expression, not in the scope of foo.


If you want to access the static members based of a function parameter, without hard-coding the type, the way forward is std::remove_reference_t<decltype(s)>. This is accepted by Clang and GCC both:

#include <type_traits>

struct S
{
constexpr static auto s_v = 42;
constexpr static auto v() { return s_v; }
};

constexpr auto foo(S const& s)
{
constexpr auto v = std::remove_reference_t<decltype(s)>::v();
return v;
}

constexpr auto bar(S const& s)
{
constexpr auto v = std::remove_reference_t<decltype(s)>::s_v;
return v;
}

int main() {}

Initialize static float constexpr member in the definition (.cpp file) is it possible

With static constexpr members you cannot leave off the initializer in the class definition. A constexpr variable must be initialized when declared because it can be used after it is declared in a constant expression. This is detailed in [class.static.data]/3 of the C++11 standard

If a non-volatile const static data member is of integral or enumeration type, its declaration in the class definition can specify a brace-or-equal-initializer in which every initializer-clause that is an assignment-expression is a constant expression ([expr.const]). 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 ([basic.def.odr]) in the program and the namespace scope definition shall not contain an initializer.

emphasis mine

So, with that, your code needs to be

// .h file
class Shape {
public:
virtual void getArea();

private:
static constexpr float pi = 3.14; // we initialize here so it can be used.
};

// .cpp file
constexpr float Shape::pi; // we define here so it can be odr-used

Do note that this has changed in C++17. With the introduction of inline variables static constexpr member variables no longer need to be defined outside of the class. The compiler will handle it for you and ensure only a single defentition of the object exists. You can still define the member if you want, but that ability is deprecated and will most likely be removed in a future standard revision. The new text for [class.static.data]/3 is

If a non-volatile non-inline const static data member is of integral or enumeration type, its declaration in the class definition can specify a brace-or-equal-initializer in which every initializer-clause that is an assignment-expression is a constant expression ([expr.const]). The member shall still be defined in a namespace scope if it is odr-used ([basic.def.odr]) in the program and the namespace scope definition shall not contain an initializer. An inline static data member may be defined in the class definition and may specify a brace-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 [depr.static.constexpr]). Declarations of other static data members shall not specify a brace-or-equal-initializer.

emphasis mine

and [dcl.constexpr]/1 says that a static constexpr variable is implicitly inline

The constexpr specifier shall be applied only to the definition of a variable or variable template or the declaration of a function or function template. The consteval specifier shall be applied only to the declaration of a function or function template. A function or static data member declared with the constexpr or consteval specifier is implicitly an inline function or variable ([dcl.inline]). If any declaration of a function or function template has a constexpr or consteval specifier, then all its declarations shall contain the same specifier.

emphasis mine

C++ inline initialize static function member

Since C++11 you can use constexpr to initialize static members of non-integral/enumeration types in the class declaration.

As @paddy comments below, this makes Bar const so it would only be a viable solution if you don't plan to modify it, what you are not doing in the question's code.

[Demo]

#include <iostream>  // cout

void X() {
std::cout << "Blah\n";
}

struct Foo {
static constexpr void(*Bar)() = X;
};

int main() {
Foo::Bar();
}


Related Topics



Leave a reply



Submit