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]
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.
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.
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.
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!
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 expressionex
is odr-used byex
unless applying the lvalue-to-rvalue conversion (4.1) tox
yields a constant expression (5.19) that does not invoke any non-trivial functions and, ifx
is an object,ex
is an element of the set of potential results of an expressione
, where either the lvalue-to-rvalue conversion (4.1) is applied toe
, ore
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.
@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";
}
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