Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

static constexpr member of same type as class being defined

I would like a class C to have a static constexpr member of type C. Is this possible in C++11?

Attempt 1:

struct Foo {
    constexpr Foo() {}
    static constexpr Foo f = Foo();
};
constexpr Foo Foo::f;

g++ 4.7.0 says: 'invalid use of incomplete type' referring to the Foo() call.

Attempt 2:

struct Foo {
    constexpr Foo() {}
    static constexpr Foo f;
};
constexpr Foo Foo::f = Foo();

Now the problem is the lack of an initializer for the constexpr member f inside the class definition.

Attempt 3:

struct Foo {
    constexpr Foo() {}
    static const Foo f;
};
constexpr Foo Foo::f = Foo();

Now g++ complains about a redeclaration of Foo::f differing in constexpr.

like image 970
ndkrempel Avatar asked Aug 13 '12 04:08

ndkrempel


People also ask

Does constexpr need to be static?

A static constexpr variable has to be set at compilation, because its lifetime is the the whole program. Without the static keyword, the compiler isn't bound to set the value at compilation, and could decide to set it later. So, what does constexpr mean?

Do constexpr variables take memory?

The alternatives don't have the all of the positives of static constexpr - you're guaranteed compile time processing, type safety, and (potentially) lower usage of memory (constexpr variables don't need to take up memory, they are effectively hard coded unless if possible).


4 Answers

If I interpret the Standard correctly, it isn't possible.

(§9.4.2/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. [...]

From the above (along with the fact that there is no separate statement about non-literal types in static data member declarations), I believe it follows that a static data member that is constexpr must be a literal type (as defined in §3.9/10), and it must have its definition included in the declaration. The latter condition could be satisfied by using the following code:

struct Foo {
  constexpr Foo() {}
  static constexpr Foo f {};
};

which is similar to your Attempt 1, but without the class-external definition.

However, since Foo is incomplete at the time of declaration/definition of the static member, the compiler can't check whether it is a literal type (as defined in §3.9/10), so it rejects the code.

Note that there is this post-C++-11 document (N3308) which discusses various problems of the current definition of constexpr in the Standard, and makes suggestions for amendments. Specifically, the "Proposed Wording" section suggests an amendment of §3.9/10 that implies the inclusion of incomplete types as one kind of literal type. If that amendment was to be accepted into a future version of the Standard, your problem would be solved.

like image 190
jogojapan Avatar answered Oct 13 '22 23:10

jogojapan


I believe GCC is incorrect to reject your Attempt 3. There is no rule in the C++11 standard (or any of its accepted defect reports) which says that a redeclaration of a variable must be constexpr iff the prior declaration was. The closest the standard comes to that rule is in [dcl.constexpr](7.1.5)/1_:

If any declaration of a function or function template has constexpr specifier, then all its declarations shall contain the constexpr specifier.

Clang's implementation of constexpr accepts your Attempt 3.

like image 39
Richard Smith Avatar answered Oct 13 '22 22:10

Richard Smith


An update on Richard Smith's answer, attempt 3 now compiles on both GCC 4.9 and 5.1, as well as clang 3.4.

struct Foo {
  std::size_t v;
  constexpr Foo() : v(){}
  static const Foo f;
};

constexpr const Foo Foo::f = Foo();

std::array<int, Foo::f.v> a;

However, when Foo is a class template, clang 3.4 fails, but GCC 4.9 and 5.1 still work ok:

template < class T >
struct Foo {
  T v;
  constexpr Foo() : v(){}
  static const Foo f;
};

template < class T >
constexpr const Foo<T> Foo<T>::f = Foo();

std::array<int, Foo<std::size_t>::f.v> a; // gcc ok, clang complains

Clang error :

error: non-type template argument is not a constant expression
std::array<int, Foo<std::size_t>::f.v> a;
                ^~~~~~~~~~~~~~~~~~~~~
like image 33
xavlours Avatar answered Oct 13 '22 21:10

xavlours


Earlier I had the same problem and came across this decade-old question. I'm happy to report that in the intervening years a solution has appeared; we just need to do something like "attempt 3" above, but mark the definition of Foo::f as inline. Minimal example which compiles with g++ --std=c++17:

foo.hpp

#ifndef __FOO_HPP
#define __FOO_HPP

struct Foo
{
    constexpr Foo() {}
    static const Foo f;
};

inline constexpr Foo Foo::f = Foo();

#endif

foo.cpp

#include "foo.h"

main.cpp

#include "foo.h"

int main(int, char **) { return 0; }
like image 24
Daniel McLaury Avatar answered Oct 13 '22 21:10

Daniel McLaury