Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

constexpr of static tuple class member has linker error

I have the following code:

#include <iostream>
#include <tuple>

class T
{
    public:
        using Names = std::tuple<char const*, char const*>;
        static constexpr Names names {"First", "Second"};
};

int main()
{
    std::cout << std::get<0>(T::names);
}

As names is a constexpr I expected this to work. But I get a linker error:

The compiler:

> g++ --version
Configured with: --prefix=/Applications/Xcode.app/Contents/Developer/usr --with-gxx-include-dir=/usr/include/c++/4.2.1
Apple LLVM version 6.0 (clang-600.0.56) (based on LLVM 3.5svn)
Target: x86_64-apple-darwin14.0.0
Thread model: posix

The error:

> g++ -std=c++1y pl.cpp
Undefined symbols for architecture x86_64:
  "T::names", referenced from:
      _main in pl-377031.o
ld: symbol(s) not found for architecture x86_64
clang: error: linker command failed with exit code 1 (use -v to see invocation)

[live demo]

like image 501
Martin York Avatar asked Jan 25 '15 08:01

Martin York


People also ask

Is it possible to redeclare static data members in C++17?

This has been fixed in C++17. If a static data member is declared constexpr, it is implicitly inline and does not need to be redeclared at namespace scope. This redeclaration without an initializer (formerly required as shown above) is still permitted, but is deprecated.

Is it common to write static const members like this?

In legacy code, it is very commonfor people to write static constmembers like this, and then simply never write the connection.cpppart. As long as you never pass DefaultTimeoutMsby reference, you’ll never notice the problem.

Do static data members need to be redeclared at namespace scope?

If a static data member is declared constexpr, it is implicitly inline and does not need to be redeclared at namespace scope. This redeclaration without an initializer (formerly required as shown above) is still permitted, but is deprecated.

What's wrong with static const variables of integral type?

The weird thing about static constmember variables of integral type is that you are allowed to move their initializing expression from the definition to the declaration!


2 Answers

A declaration of a static data member in class is never a definition1.
A definition is necessary whenever a variable is odr-used2. std::get<> takes arguments per reference, and binding a variable to a reference odr-uses it immediately3.

Simply define names outside:

constexpr T::Names T::names; // Edit: This goes *outside* the class "as is"!

Demo.


1) [basic.def]/2:

A declaration is a definition unless [..] it declares a static data member in a class definition (9.2, 9.4)

2) [basic.def.odr]/4:

Every program shall contain exactly one definition of every non-inline function or variable that is odr-used in that program; no diagnostic required.

3) According to [basic.def.odr]/3:

A variable x whose name appears as a potentially-evaluated expression ex is odr-used by ex unless applying the lvalue-to-rvalue conversion (4.1) to x yields a constant expression (5.19) that does not invoke any non-trivial functions and, if x is an object, ex is an element of the set of potential results of an expression e, where either the lvalue-to-rvalue conversion (4.1) is applied to e, or e is a discarded-value expression (Clause 5).

Here the id-expression T::names refers to the variable in question. The only superexpression e that contains all the potential results of T::names is T::names itself, because the set of potential results of a function call, i.e. std::get<0>(T::names), is empty. However, the lvalue-to-rvalue conversion is clearly not applied, and the value of T::names is also clearly not discarded (as it is passed to a function).
Thus it is odr-used and requires a definition.

like image 175
Columbo Avatar answered Nov 14 '22 22:11

Columbo


@Columbo posted the correct solution.

Unfortunately I am trying to build a header only library. The solution requires that the static member be compiled into one compilation unit (which is what I was using constexpr in the hopes of avoiding).

So I needed to stick another twist into the works to make it work. This is just to share my solution:

#include <iostream>

class T
{
    public:
        using Names = std::tuple<char const*, char const*>;
        template<std::size_t index>
        static char const* getName()
        {
            static constexpr Names names {"First", "Second"};
            return std::get<index>(names);
        }
};

int main()
{
    std::cout << T::getName<0>() << "\n";
}
like image 40
Martin York Avatar answered Nov 14 '22 23:11

Martin York