Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

static constexpr template member gives undefined-reference when specialized

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

like image 701
wimalopaan Avatar asked Dec 19 '17 11:12

wimalopaan


2 Answers

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 the constexpr 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);
}
like image 57
AndyG Avatar answered Oct 16 '22 12:10

AndyG


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.

like image 4
StoryTeller - Unslander Monica Avatar answered Oct 16 '22 10:10

StoryTeller - Unslander Monica