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
.