52

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.

5 Answers 5

38

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.

Sign up to request clarification or add additional context in comments.

3 Comments

I've just come up against this exact problem. It seems inconsistent with how static const can work with incomplete types. I guess I'll just have to make do with a static const for now!
This is not strictly WRONG, but not as helpful an answer as the one from Richard Smith. Declare as const and then define just below as constexpr. AND - please note - this now applies to inline defitions as well as constexpr
Has this changed with C++20? Do you know the status of the amendment suggestions?
20

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.

6 Comments

Perhaps, but in Attempt 3, the variable is only const and not a constexpr, isn't it?
In Attempt 3, the variable is constexpr, because constexpr was specified in the definition. The relevant section of the standard is 5.19/2: "an lvalue-to-rvalue conversion [...may be applied to...] a non-volatile glvalue of literal type that refers to a non-volatile object defined with constexpr
Are you sure that's allowed? A static member variable can't be introduced outside the class, it first has to be declared inside the class with the same type (including cv-qualifiers). I couldn't seem to find the rule which requires that, however, in order to see whether constexpr is excluded from the signature matching.
Ok, part of the rule is in 8.3p1: "When the declarator-id is qualified, the declaration shall refer to a previously declared member of the class or namespace to which the qualifier refers" I still can't find the bits which specify how well the type and qualifiers have to match.
You're looking for 3.5p10: "After all adjustments of types (during which typedefs (7.1.3) are replaced by their definitions), the types specified by all declarations referring to a given variable or function shall be identical, except that declarations for an array object can specify array types that differ by the presence or absence of a major array bound". Note that constexpr adds const to the type, but is not itself part of the type.
|
14

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;
                ^~~~~~~~~~~~~~~~~~~~~

3 Comments

Unfortunately, if you put the definition in your .h file, you end up with "multiple definition" errors at link time. And if you put it in your C++ file, other C++ files don't know that it is constexpr.
@MartinC.Martin That's the reason for using the template, or if using C++17 you can use inline variables. Clang and MSVC currently choke on using the template as a compile-time constant expression though. Using inline variables, this works on GCC, Clang, and MSVC (relevant versions of each, of course).
@monkey0506 I'm having a similar issue as this. Could you show how to do this using C++17 inline variables?
2

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

7 Comments

This doesn't seem to be portable. It works with GCC and Clang, but MSVC fails with error LNK2005: "public: static class ... already defined in ... learn.microsoft.com/en-us/cpp/error-messages/tool-errors/…
@aij I just tried this right now on Visual Studio and it built fine for me. Like for gcc/clang you need to pass /std:c++17 to MSVC, which I did via (right click on project in Solution Explorer) -> Properties -> Configuration Properties -> C/C++ -> Language -> C++ Language Standard
BTW: #define __FOO_HPP invokes undefined behavior because you aren't allowed to use double underscores in your symbols (same with leading underscore followed by a capital). en.cppreference.com/w/cpp/language/identifiers (I'm 99.9% true this applies to macro names as well.)
I had been happily using this for one year (GCC/Clang/MSVC in C++17 mode), but unfortunately I just discovered that it doesn't compile with MSVC (both latest MSVC 2019 and latest MSVC 2022) with the /permissive- flag, which is enabled by default in C++20 mode, and is anyway recommended in C++17 mode too. So I'm back to looking for the best approach :/
There is actually an open MSVC bug report as to the fact that it doesn't compile with the /permissive- flag. I have added a comment there and linked to this SO thread. See: developercommunity.visualstudio.com/t/…
|
0

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.

2 Comments

Why not simply use a nested class? Like so: stackoverflow.com/a/70048197/7107236
That helps a little, since it’s logicLly part of the enclosing class and since you don’t have to forward declare anything, but still… I just want the static member to be the same type like enum can do.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.