1

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

digito_evo
  • 3,216
  • 2
  • 14
  • 42
  • I'm curious to know: 1. Why do you need to initialize `var` outside the class definition; 2. Why is `var` marked as `inline` if you initialize it outside the class definition? – paolo Aug 10 '22 at 09:50
  • @paolo inline is there just to make it look similar to its actual definition outside the class which has the inline keyword too, Also I need to initialize it outside the class because the client will decide on the value. There are many of these variables in my actual class. And I can't pass them to the class template as template arguments because it will look bloated. – digito_evo Aug 10 '22 at 09:53

2 Answers2

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

StoryTeller - Unslander Monica
  • 165,132
  • 21
  • 377
  • 458
  • I tested this on Compiler Explorer and played around a bit with it. I believe that it is a brilliant solution for the particular problem that I have at hand. It's also very similar to my own code that I have put in the question. Actually, I have been wondering for a while if this problem can be solved using variable templates and there we are, you showed me the correct way! Thank you very very much! – digito_evo Aug 10 '22 at 10:42
1

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.

francesco
  • 7,189
  • 7
  • 22
  • 49