Home > Software engineering >  static_assert not working inside class template definition
static_assert not working inside class template definition

Time:08-10

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>.

CodePudding user response:

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.

CodePudding user response:

The member var should be declared constexpr, and so, it needs to be initialized directly.

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

template < std::unsigned_integral size_type >
class Foo
{
public:
    static constexpr size_type var = 10;

    static_assert( var <= 20, "Error" );
};

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

See it live on Coliru.

I removed the inline because it is not necessary. Please refer to this answer for a discussion of the difference between static const and constexpr.

  • Related