Search code examples
c++constexprstatic-assertclass-template

static_assert not working inside class template definition


I'm trying to define a static member variable outside the class definition. It works as intended. But the static_assert that I placed inside the class definition does not compile for some reason. Why?

The error message is:

note: 'Foo<unsigned int>::var' was not initialized with a constant expression

Commenting out the static_assert statement lets the code compile.

The code (link):

#include <iostream>
#include <cstdint>
#include <concepts>

template < std::unsigned_integral size_type >
class Foo
{
public:
    inline static const size_type var;

    static_assert( var <= 20, "Error" ); // note: 'Foo<unsigned int>::var' was not
                                         // initialized with a constant expression
};

template <>
inline constexpr std::uint32_t Foo<std::uint32_t>::var { 10 };

int main( )
{
    return Foo<std::uint32_t>::var;
}

Is there a way to fix this? Or should I place the static_assert outside the class definition and after the definition of Foo<T>::var?

Note: I might have to mention that the reason for static_assert being inside the class body is to avoid code duplication. Otherwise, I would have to write that static assert statement after the definition of var for every instantiation of Foo<T>.


Solution

  • inline static const size_type var;
    

    That's all fine and dandy, but it does not mean that var is usable in a constant expression for every instantiation. There's the famous (infamous?) [temp.res.general]/8

    The validity of a template may be checked prior to any instantiation.
    The program is ill-formed, no diagnostic required, if:

    • a hypothetical instantiation of a template immediately following its definition would be ill-formed due to a construct that does not depend on a template parameter, or

    That's ill-formed NDR; but a compiler is allowed to check. A specialisation will not be available immediately following the primary definition, so while you may specialize to make the member constexpr, it doesn't save the primary template from running foul of this condition.

    I'd use a trait and invert the order.

    template<typename T>
    inline constexpr T FooSizeTraitValue = 10000;
    
    template<>
    constexpr std::uint32_t FooSizeTraitValue<std::uint32_t> = 10;
    
    template < std::unsigned_integral size_type >
    class Foo
    {
    public:
        static constexpr size_type var = FooSizeTraitValue<size_type>;
    
        static_assert( var <= 20, "Error" );
    };
    

    This should give you the same ability to specialise for different types as you wanted, without poisoning the body of the primary template unconditionally.