Which compiler is right about inline static members of template parameter type using a Singleton CRTP?

Viewed 91

I made a very simple Singleton base class that can be used with CRTP to make any class that derives from it a singleton. I found something strange though. Different compilers disagree on whether the code is ill-formed or not. Here it is:

template <typename Derived>
class Singleton
{
public:
    inline static Derived instance;
};

As I said, it is very simple, but that is enough for demonstration purposes. An example usage is shown here:

class MyClass : public Singleton<MyClass>
{
public:
    std::string msg;

    MyClass() = default;
    MyClass(const std::string& msg_) : msg{ msg_ }
    {
    }
    MyClass(std::string&& msg_) noexcept : msg{ std::move(msg_) }
    {
    }
};

This code compiles under both GCC, and Clang. However MSVC rejects it by saying that MyClass provides no appropriate default constructor (despite it obviously being declared as default). Out of curiosity I tried using a different constructor:

template <typename Derived>
class Singleton
{
public:
    inline static Derived instance{"hello MSVC"};
};

class MyClass : public Singleton<MyClass>
{
public:
    std::string msg;

    MyClass() = default;
    MyClass(const std::string& msg_) : msg{ msg_ }
    {
    }
    MyClass(std::string&& msg_) noexcept : msg{ std::move(msg_) }
    {
    }
};

This is again accepted by GCC and Clang, but rejected by MSVC, which claims this time that MyClass is undefined.

The question is: Is this code ill-formed? If it is, why? And consequently, which compilers are right? Are GCC and Clang simply taking advantage of "no diagnostic required"?

My gut feeling is that MSVC is wrong, as this is usually the case, though on the other hand, it is usually more permissive than GCC and Clang, so I am curious of the answer.

1 Answers

GCC and Clang are correct.

temp.inst/3:

The implicit instantiation of a class template specialization causes

  • the implicit instantiation of the declarations, but not of the definitions, of the non-deleted class member functions, member classes, scoped member enumerations, static data members, member templates, and friends; and
  • the implicit instantiation of the definitions of deleted member functions, unscoped member enumerations, and member anonymous unions.

In the context of the Singleton<Derived> instantiation, it is true that Derived is incomplete, but that doesn't matter because it's ok to declare with an incomplete type.

See also: gcc bug #71534

Related