The following code gives an undefined reference linking error:
template<int>
struct X {
static constexpr int x = 0;
};
template<>
constexpr int X<1>::x;
int main()
{
return X<1>::x;
}
But I don't know exactly why.
Is it possible to define a data-member without to specialize the whole template?
To be clear: this code compiles fine, but gives a linker error (undefined-reference).
Is it possible to define a data-member without [specializing] the whole template?
static
data members of a class template are allowed to be explicitly specialized ([temp.expl.spec]), however if you wish to do this, you cannot already specify an initializer for the member within the class template (class.static.data). That is,
if we replace constexpr
with const
, this code would be fine:
template<int>
struct X {
static const int x;
};
template<int I>
const int X<I>::x = 0;
template<>
const int X<1>::x = 1;
But this code would NOT be fine:
template<int>
struct X {
static const int x = 0;
};
template<>
const int X<1>::x = 1;
You can see the difference is where we initialize the variable for the primary template.
Now, if we wish to replace const
with constexpr
, then we're required to provide an initializer (class.static.data):
A
static
data member of literal type can be declared in the class definition with theconstexpr
specifier; if so, its declaration shall specify a brace-or-equal-initializer in which every initializer-clause that is an assignment-expression is a constant expression
So we end up in this weird situation where we can specialize the static
member, but not if it is constexpr
because constexpr
requires an initializer. IMHO this is a shortcoming of the standard.
However, it doesn't appear that all modern compilers agree.
gcc 8.0.0 compiles (but doesn't link) your code as-is (wrong), however if you add an initializer for the specialization it complains about duplicate initialization (right).
clang 6.0.0 doesn't compile the code as-is (right), but when you add the initializer it works without a hitch (wrong, but this is probably what the standard should dictate)
MSVC 19.00.23506 doesn't compile the code as-is (right), AND it doesn't compile the code when you add the initializer (complaining about redefinition) (right).
In the end, it might just be easier to push the specialization into a helper Traits class:
template<int>
struct X_Traits{
static constexpr int value = 0;
};
template<>
struct X_Traits<1>{
static constexpr int value = 1;
};
template<int I>
struct X {
static constexpr int x=X_Traits<I>::value;
// ...
};
In C++17 and beyond we can make use of constexpr if to avoid needing to specialize our traits class:
template<int I>
struct X_Traits{
static constexpr int get_value(){
if constexpr(I==1){
return 1;
}else{
return 0;
}
}
};
template<int I>
struct X {
static constexpr int x=X_Traits<I>::get_value();
// ...
};
int main(){
static_assert(X<0>::x == 0);
static_assert(X<1>::x == 1);
}
You stumbled upon a "small" issue due to explicitly specializing. If we refer to [temp.expl.spec]/13:
An explicit specialization of a static data member of a template or an explicit specialization of a static data member template is a definition if the declaration includes an initializer; otherwise, it is a declaration. [ Note: The definition of a static data member of a template that requires default-initialization must use a braced-init-list:
template<> X Q<int>::x; // declaration template<> X Q<int>::x (); // error: declares a function template<> X Q<int>::x { }; // definition
— end note ]
Meaning that you declare X<1>::x
to exist, but not define it. Hence it's undefined.
What I find crazy, is that your compiler just accepts it. You can't declare constexpr
variables without defining them, in general. This is quite odd.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With