Search code examples
c++language-lawyerconstexprc++23constinit

Static data member of template class type: constexpr vs. const constinit


I have a class:

#include <array>

template<class T, std::size_t N>
    requires std::is_arithmetic_v<T> && (N >= 1)
class Vector
{
    static constexpr std::size_t Dimension = N;
    std::array<T, Dimension> Elements;

public:
    constexpr Vector() noexcept : Elements{} {}
    constexpr ~Vector() = default;
    static constexpr Vector ZeroVector{};
};

int main()
{
    Vector<float, 7> boo = Vector<float, 7>::ZeroVector;
}

The above fails to compile on compiler explorer with MSVC and Clang (trunk) with C++23 compiler flags, but it compiles on GCC (trunk) with C++23 compiler flags.

Clang gives the following error:

<source>:13:29: error: constexpr variable cannot have non-literal type 'const Vector<float, 7>'
   13 |     static constexpr Vector ZeroVector{};
      |                             ^
<source>:18:28: note: in instantiation of template class 'Vector<float, 7>' requested here
   18 |     Vector<float, 7> boo = Vector<float, 7>::ZeroVector;
      |                            ^
<source>:13:29: note: incomplete type 'const Vector<float, 7>' is not a literal type
   13 |     static constexpr Vector ZeroVector{};
      |                             ^
<source>:5:7: note: definition of 'Vector<float, 7>' is not complete until the closing '}'
    5 | class Vector
      |       ^
1 error generated.
Compiler returned: 1

MSVC gives the following error:

<source>(13): error C2027: use of undefined type 'Vector<float,7>'
<source>(5): note: see declaration of 'Vector<float,7>'
<source>(13): note: the template instantiation context (the oldest one first) is
<source>(18): note: see reference to class template instantiation 'Vector<float,7>' being compiled
Compiler returned: 2

When I change constexpr to const constinit for ZeroVector, this compiles on all three major compilers when the definition is moved outside the class like so:

template<class T, size_t N> requires std::is_arithmetic_v<T> && (N >= 1)
const constinit Vector<T, N> Vector<T, N>::ZeroVector{};

So why does constexpr compile only on GCC and const constinit compiles on all three major compilers?


Solution

  • This is an old gcc bug and the program is ill-formed.

    When using constexpr or inline with the declaration of a static data member, the type must be a complete type. But Vector<T,N> is not complete at the point of the static data member's definition/declaration.

    From static data member:

    However, if the declaration uses constexpr or inline (since C++17) specifier, the member must be declared to have complete type.


    Here is the old gcc bug report:

    static constexpr incomplete (depedent) data member of a class template and in-member initialized incorrectly accepted