26

Consider this code:

#include <vector>

struct A {
  static constexpr int kDefaultValue = -1;
  std::vector<int> v;
  A(int n): v(n, A::kDefaultValue) {}
};

int main() {
  A(10);
  return 0;
}

It fails to link (llvm clang, gcc 4.9, both on OS X):

Undefined symbols for architecture x86_64:
  "A::kDefaultValue", referenced from:
      A::(int) in main.cpp.o
ld: symbol(s) not found for architecture x86_64

The question is what's wrong with it? It can be fixed by static_cast-ing A::kDefaultValue to int. Or by moving kDefaultValue out of A. Both cases seem to be ugly. Is this another way to make it link?

songyuanyao
  • 169,198
  • 16
  • 310
  • 405
y0prst
  • 611
  • 1
  • 6
  • 13
  • 1
    Use a compiler supporting C++17. – songyuanyao Nov 19 '16 at 07:30
  • what are you trying to do here -> `A(int n): v(n, A::kDefaultValue)` Why no just do `A(int n): v(n,)` ? and use std=c++11 as compiler option – solti Nov 19 '16 at 07:31
  • songyuanyao: tried gcc-6 with c++14, c++17, same thing. – y0prst Nov 19 '16 at 07:36
  • @y0prst It's fine with gcc7. http://melpon.org/wandbox/permlink/izFx4rwqLyX8QR7x – songyuanyao Nov 19 '16 at 07:39
  • @solti: All I want is vector of `-1`-s and also I want to take name to this `-1` constant. `-std=c++11` does not help, as well as `c++14` and `c++17`. – y0prst Nov 19 '16 at 07:39
  • @songyuanyao: Nice! I wonder what was changed in c++17 in order to make this code work. Don't believe that this is just a compiler bug :) – y0prst Nov 19 '16 at 07:47
  • @y0prst It's not a bug, the behavior changed from C++17. See my answer for details. – songyuanyao Nov 19 '16 at 07:53
  • 1
    Another _workaround_ is to change `kDefaultValue` to `static constexpr std::integral_constant kDefaultValue{};`. No external definition is needed and `v(n, A::kDefaultValue)` will work correctly, and it works for C++11 onwards. [Online Demo](http://coliru.stacked-crooked.com/a/602086f995f56e53) – ildjarn Nov 19 '16 at 17:10

2 Answers2

31

This behaviour is vexing me time and again. The cause of the trouble is that your

A(int n): v(n, A::kDefaultValue) {}

odr-uses the static constexpr member, since the constructor of v takes a constant reference second argument. Odr-usage requires a definition somewhere, i.e.

const int A::kDefaultValue;

in some compilation unit (which is compiled and linked to main()). This requirement has been dropped in C++17 and the corresponding definition (as above) deprecated.

However, a definition is not always possible (for example for members of class templates) and the simplest way to avoid both the definition and your error is

A(int n): v(n, int(A::kDefaultValue)) {}

which creates a temporary to be passed to the constructor of v (but since the latter is fully inline, the compiler may optimise that away).

Walter
  • 44,150
  • 20
  • 113
  • 196
  • Is it worth to use constexpr method instead of static member? `static constexpr int default_value() { return -1; };` Seems it works in all standards with no drawbacks. – y0prst Nov 19 '16 at 10:46
  • Yes, that's another possibility; and with all these things it's a matter of taste and style. The standard library uses `static` members in its [numeric_limits<>](http://en.cppreference.com/w/cpp/types/numeric_limits) traits classes. – Walter Nov 19 '16 at 11:00
  • BTW, it's strange, that for example using `numeric_limits::radix`, which is `static constexpr` member of `struct numeric_limits`, does not fail. – y0prst Nov 19 '16 at 11:17
  • @y0prst That's because implementations of the standard library provide a definition of these static variables somewhere (which the linker then finds). – Walter Nov 20 '16 at 16:49
13

The behavior changed since C++17. Before C++17, even a constexpr static data member must be initialized inside the class definition, definition at namespace scope is still required; Since C++17 the namespace scope definition is not required again.

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. (since C++17)

Compiling your code with a compiler supporting C++17 would work fine.

LIVE demo with gcc7

songyuanyao
  • 169,198
  • 16
  • 310
  • 405