0

What is the best way to define a constexpr variable scoped within its own class?

Conceptually, what I want is this:

struct Meow
{
    // constructors etc

    static constexpr Meow Zero{ 0 };
};

However this is not legal because within the body of the class itself Meow is still an incomplete type and cannot be constexpr-constructed (as dumb as that seems).


This works, but isn't constexpr and can't be inlined:

// in header file
struct Meow
{
    // ...
    static const Meow Zero;
};

// in cpp file
const Meow Meow::Zero{ 0 };

This also works, but isn't scoped as desired:

struct Meow
{
    // ...
};
inline constexpr Meow Meow_Zero{ 0 };

This seems to almost work, but generates linker duplicate symbol warnings when the header is used from multiple translation units:

struct Meow
{
    // ...
    static const Meow Zero;
};
inline constexpr Meow Meow::Zero{ 0 };

resulting in:

meow2.obj : warning LNK4006: "public: static struct Meow const Meow::Zero" (?Zero@Meow@@2U1@B) already defined in meow1.obj; second definition ignored

or sometimes:

cat.lib(meow2.obj) : error LNK2005: "public: static struct Meow const Meow::Zero" (?Zero@Meow@@2U1@B) already defined in cat.lib(meow1.obj)

(Declaring it inline const rather than inline constexpr has the same result.)

And yet, this last syntax works without issues if Meow is a template instead (since templates get a secret ODR backdoor).

Miral
  • 12,637
  • 4
  • 53
  • 93
  • 3
    "*However this is not legal because within the body of the class itself Meow is still an incomplete type and cannot be constexpr-constructed (as dumb as that seems).*" What is "dumb" about that? The type isn't finished yet. For all the compiler knows, you might introduce another constructor that would make the initialization call a different function. The compiler has no idea how much storage such an object takes up because the type is incomplete. – Nicol Bolas Oct 13 '21 at 06:36
  • It could defer the construction until the end of the class body. – Miral Oct 13 '21 at 06:47

1 Answers1

1

As hinted at the end of the question: indirecting via a dummy template does seem to work, but I'm not happy about it (and am open to better ideas):

template<typename T>
struct MeowImpl
{
    // ...
    static const MeowImpl Zero;
};

template<typename T>
inline constexpr MeowImpl<T> MeowImpl<T>::Zero{ 0 };

using Meow = MeowImpl<void>;

(This also works if inline is omitted, again because template. Isn't C++ fun?)

Miral
  • 12,637
  • 4
  • 53
  • 93