3
#include <iostream>
using namespace std;

template<typename T> void print(T&& mX) 
{
    std::cout << std::forward<T>(mX) << std::endl;  
}

struct SomeStruct
{
    static constexpr const char* someString{"hello!"};
    SomeStruct()
    {
        print(someString);
    }
};

int main() 
{
    SomeStruct s{};
    return 0;
}

clang++ -std=c++1y ./code.cpp -o code.o

/tmp/code-a049fe.o: In function `SomeStruct::SomeStruct()': ./code.cpp:(.text._ZN10SomeStructC2Ev[_ZN10SomeStructC2Ev]+0xa): undefined reference to `SomeStruct::someString' clang: error: linker command failed with exit code 1 (use -v to see invocation)


g++ -std=c++1y ./code.cpp -o code.o

/tmp/ccyrTsjS.o: In function `SomeStruct::SomeStruct()': code.cpp:(.text._ZN10SomeStructC2Ev[_ZN10SomeStructC5Ev]+0xd): undefined reference to `SomeStruct::someString' collect2: error: ld returned 1 exit status


Why is this linker error happening? Isn't someString supposed to be resolvable at compile-time?

Also, the error doesn't happen if print(someString) is replaced with cout << someString;

Lightness Races in Orbit
  • 378,754
  • 76
  • 643
  • 1,055
Vittorio Romeo
  • 90,666
  • 33
  • 258
  • 416

2 Answers2

5

Because you are taking a reference the variable is odr-used and this requires a definition out of line:

constexpr const char* SomeStruct::someString;

see it working live.

From the draft C++14 standard section 3.2 [basic.def.odr]:

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.20) that does not invoke any nontrivial 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 [...]

For example the following alternative print would not odr-use someString:

template<typename T> void print(T mX) 
{
    std::cout << mX << std::endl;  
}
Shafik Yaghmour
  • 154,301
  • 39
  • 440
  • 740
2

There are a narrow set of circumstances under which you may not bother to define a static data member (whether it's constexpr or not, and whether it takes advantage of this initialisation form or not), and this is not one of them, because you indirectly take the address of it (and therefore it must exist as an actual object in memory).

You can kind of think of it as a static data member that can't simply be "inlined" at the point of its use, due to what that use is.

Define someString.

Lightness Races in Orbit
  • 378,754
  • 76
  • 643
  • 1,055
  • Thanks. Is a `static constexpr const char*` the closest thing to a possible `static constexpr StringKnownAtCompileTimeType`, or is there a way to actually tell the compiler and the people who read my code that I want a string that is known at compile-time? – Vittorio Romeo Mar 03 '15 at 19:08
  • @VittorioRomeo: It _is_ known at compile-time. But you can't take advantage of that with the code you've shown us. – Lightness Races in Orbit Mar 03 '15 at 19:17