army007
army007

Reputation: 561

How to use make_tuple() in static constexpr class member?

I am trying to use make_tuple() in constexpr. It works in global scope. But it generates link error for static constexpr class member.

#include <iostream>
#include <tuple>

using namespace std;

class A
{
public:
    static constexpr auto z = make_tuple(5, 3.0);
};

constexpr auto tp = make_tuple(6, 3.2);

int main()
{

    cout << get<0>(tp) << " " << get<1>(tp) << endl; // OK

    cout << get<0>(A::z) << "  " << get<1>(A::z) << endl; // error: (.text+0x5a): undefined reference to `A::z'
                                                          //        (.text+0x67): undefined reference to `A::z'

}

I have checked here make_tuple is not itself a constexpr in c++11. I guess that is not the problem in this case. If it was it would generate a compile error instead of link error.

I have tried to define the constexpr outside the class like bellow as suggested by this answer

class A
{
public:
    static constexpr tuple<int, double> z;
};

constexpr tuple<int, double> A::z = make_tuple(5, 3.0);

But, it generates several compile error. That makes sanse according to answers of constexpr initializing static member using static function

What is the correct way to use make_tuple in static constexpr class member?

Compiler: g++ 4.8.4 and clang 3.4 with -std=c++11

Upvotes: 2

Views: 1458

Answers (1)

Yakk - Adam Nevraumont
Yakk - Adam Nevraumont

Reputation: 275415

In C++11/14, static constexpr variables must be defined somewhere if ODR-used (ODR-used means "used in a way that requires it have an identity". As an example, take a real reference or pointer to them. ODR here means "one definition rule", and ODR-used means "used in a sense that requires it to have exactly one definition", which I shorten to "have an identity").

Simply add:

constexpr tuple<int, double> A::z;

and we have defined where it exists. This needs to be in exactly one compilation unit.

In C++17, inline variables were added and I believe constexpr variables were implicitly made inline. I'm not a C++17 expert, but it compiles and runs in C++1z mode without the definition, so my interpretation seems at least half correct.

(inline variables, like inline functions, have the definition in the end created wherever the compiler wants. inline functions are typically implemented by having a definition in every object file with special notes when someone takes an address of it, then the duplicates are discarded at link time and everyone refers to the last one standing. I don't know if inline variables tend to be implemented the same way or not.)

If you want to solve this problem in C++11 without having to put something in a .cpp file with hardcoded template arguments, change z to a constexpr function and call it when you want to use it.


As SergyA has mentioned, ODR is triggered here because get returns a reference to a field in the tuple in question, and that reference implies an identity must exist for the field and hence the entire tuple.

In theory, a carefully crafted value_get that returned by value could avoid this ODR-usage, but it would depend on the implemenation of tuple as it in turn could not call get.

Upvotes: 5

Related Questions