Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Undefined reference, template struct and constexpr static member

I got some problem with a static constexpr member of a template struct. The code compiles but I get linking error. Here's what I'm trying to do:

template<int n>
struct Test {
    static constexpr auto invoke = make_tuple(2, "test", 3.4);
};

template<typename T>
void test(T&& t) {
    cout << t << endl;
}

int main() {
    test(get<0>(Test<2>::invoke));
    return 0;
}

I got linking errors with that, so I tried this:

template<int n>
struct Test {
    static constexpr auto invoke = make_tuple(2, "test", 3.4);
};

// declare it outside the class
template<int n>
constexpr decltype(Test<n>::invoke) Test<n>::invoke;

template<typename T>
void test(T&& t) {
    cout << t << endl;
}

int main() {
    test(get<0>(Test<2>::invoke));
    return 0;
}

But instead I got this strange error:

error: redefinition of 'invoke' with a different type: 'const decltype(Test<n>::invoke)' vs 'const std::tuple<int, const char *, double>'

A different type?? Obviously, the non-template version works just fine:

struct Test {
    static constexpr auto invoke = make_tuple(2, "test", 3.4);
};

constexpr decltype(Test::invoke) Test::invoke;

template<typename T>
void test(T&& t) {
    cout << t << endl;
}

int main() {
    test(get<0>(Test::invoke));
    return 0;
}

How can I get the template version to work? Thank you very much

like image 321
Guillaume Racicot Avatar asked Oct 26 '15 03:10

Guillaume Racicot


2 Answers

It looks like you are running into an interesting corner case with decltype, this is covered in the clang bug report Static constexpr definitions used inside template which has the following example with a similar error as yours:

This compiles fine, but when I make the class A, a template like this:

struct L
{
    void operator()() const
    {}
};

template<class X>
struct A
{
    static constexpr auto F = L();
};

template<class X>
constexpr decltype(A<X>::F) A<X>::F;

int main()
{
    A<void>::F();
    return 0;
}

Clang crashes, if I change the definition line for F to this:

template<class X>
constexpr typename std::remove_const<decltype(A<X>::F)>::type A<X>::F;

Then clang produces this error:

error: redefinition of 'F' with a different type
constexpr typename std::remove_const<decltype(A<X>::F)>::type A<X>::F;
                                                                    ^
note: previous definition is here
    static constexpr auto F = L();
                          ^

and Richard Smith's reply was as follows:

This error is correct. 'constexpr' and 'auto' are red herrings here. Reduced testcase:

template<class X> struct A { static int F; };
template<class X> decltype(A<X>::F) A<X>::F;

Per C++11 [temp.type]p2, "If an expression e involves a template parameter, decltype(e) denotes a unique dependent type." Therefore the type of A::F does not match the type in the template.

the full quote for that from the C++14 draft is as follows:

If an expression e involves a template parameter, decltype(e) denotes a unique dependent type. Two such decltype-specifiers refer to the same type only if their expressions are equivalent (14.5.6.1). [ Note: however, it may be aliased, e.g., by a typedef-name. —end note ]

The only obvious way I can see to get this work is:

template<int n>
constexpr decltype(make_tuple(2, "test", 3.4)) Test<n>::invoke;

No work around was offered in the bug report.

like image 91
Shafik Yaghmour Avatar answered Sep 22 '22 07:09

Shafik Yaghmour


How can I get the template version to work?

FWIW, your method works fine on my desktop, which uses g++ 4.8.4.

You can use:

template<int n>
constexpr decltype(make_tuple(2, "test", 3.4)) Test<n>::invoke;

The following also works on my desktop:

template<int n>
struct Test {
    static constexpr auto invoke = make_tuple(2, "test", 3.4);
    typedef decltype(invoke) invoke_t;
};

template<int n>
constexpr typename Test<n>::invoke_t Test<n>::invoke;
like image 43
R Sahu Avatar answered Sep 22 '22 07:09

R Sahu