Reputation: 1936
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
.
Upvotes: 51
Views: 9106
Reputation: 4273
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; }
Upvotes: 2
Reputation: 9703
If, like me, you are trying to make enum
-like classes, the best I've figured out is to use CRTP to put the behavior in a base class and then a derived class is the "real" class that exists just to have the "enumerator"-like values as static constexpr inline Base
members. This means that Foo::yes
isn't of type Foo
, but it acts like Foo
and is implicitly convertible to Foo
, so it seems pretty close. https://godbolt.org/z/rTEdKxE3h
template <class Derived>
class StrongBoolBase {
public:
explicit constexpr StrongBoolBase() noexcept : m_val{false} {}
explicit constexpr StrongBoolBase(bool val) noexcept : m_val{val} {}
[[nodiscard]] constexpr explicit operator bool() const noexcept { return m_val; }
[[nodiscard]] constexpr auto operator<=>(const StrongBoolBase&) const noexcept = default;
[[nodiscard]] constexpr auto operator not() const noexcept {
return StrongBoolBase{not this->asBool()};
}
[[nodiscard]] constexpr bool asBool() const noexcept {
return m_val;
}
private:
bool m_val;
};
template <class Tag>
class StrongBool : public StrongBoolBase<StrongBool<Tag>> {
using Base = StrongBoolBase<StrongBool<Tag>>;
public:
//////// This is the interesting part: yes and no aren't StrongBool:
inline static constexpr auto yes = Base{true};
inline static constexpr auto no = Base{false};
using Base::Base;
constexpr StrongBool(Base b) noexcept : Base{b} {}
};
The only breakdown is if you start to use decltype(Foo::yes)
as though it's a Foo
.
Upvotes: 0
Reputation: 753
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;
^~~~~~~~~~~~~~~~~~~~~
Upvotes: 14
Reputation: 14158
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 theconstexpr
specifier.
Clang's implementation of constexpr
accepts your Attempt 3.
Upvotes: 20
Reputation: 69957
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.
Upvotes: 37