2

Consider the following piece of code.

class aClass
{
public:
    static const int HALLO = -3;
};

int main()
{
  std::vector<double > a;
  std::vector<int> b;
  std::vector<int> c;
  int d = aClass::HALLO; //fine
  a.resize(10,aClass::HALLO); //fine
  b.resize(10,aClass::HALLO); // linker error c++11 and c++14
  c.resize(10,(int)(double)aClass::HALLO); //fine
  std::cout<<a[0]<<endl;
  std::cout<<b[0]<<endl;
  std::cout<<c[0]<<endl;
  return 0;
}

Compiling works with C++03 and yields the output:

-3
-3
-3

Compiling with C++11 or C++14, however, leads to a linker error:

/tmp/cc3BARzY.o: In Funktion `main':
main.cpp:(.text+0x66): Nicht definierter Verweis auf `aClass::HALLO'
collect2: error: ld returned 1 exit status

Weirdly enough, this only happens for vector b. If there is a cast to double(a) or even to double and back to int (c), the code runs as expected.

How can this behaviour be explained?

ildjarn
  • 62,044
  • 9
  • 127
  • 211
Ricardo
  • 245
  • 1
  • 9
  • related: https://stackoverflow.com/questions/272900/undefined-reference-to-static-class-member. I'm trying to find a target that talks about ODR though. – NathanOliver May 24 '18 at 15:29
  • I wonder that `class aClass { public: static const int HALLO = -3; };` compiled with C++03. (I didn't know this.) However, I fixed this (and other issues) of your MCVE: [**coliru**](http://coliru.stacked-crooked.com/a/7fb8f3fcbaf23ab6) – Scheff's Cat May 24 '18 at 15:29
  • @Scheff : Pre-C++11, _only_ static data members of const integral type were possible to initialize inline; C++11 relaxed this restriction to allow more types. – ildjarn May 24 '18 at 15:46

2 Answers2

7

Until C++11 the signature of std::vector::resize() was

void resize( size_type count, T value = T() );

Now it is instead

void resize( size_type count, T const& value );

The change from pass-by-value to pass-by-const-ref causes the callsite to ODR-use aClass::HALLO where previously it did not. Casting to double then back to int yields a temporary in a way that avoids ODR-use; the call to a.resize() works for the same reason, as the int value is implicitly cast to a double and the argument reference is bound to the resulting temporary.

The usual fix here is to provide a definition for aClass::HALLO; if for some reason that's undesirable for you, a shorthand for yielding a temporary to avoid ODR-use is to apply unary operator+:

b.resize(10, +aClass::HALLO);
ildjarn
  • 62,044
  • 9
  • 127
  • 211
  • 1
    ... which is done by adding `const int aClass::HALLO;` in the program. – YSC May 24 '18 at 15:35
  • Thanks, that was what I was looking for! Since I like both answers equally, I accepted the one from the user with less reputation. – Ricardo May 25 '18 at 08:57
3

The reason why it works for double vector, but not int is funny. The signature for std::vector::resize is void resize(size_type count, const value_type& value ) since C++11. Taking a reference to the object makes it ODR-used, and because of that, your static int member now needs to be defined somewhere in your application.

However, when you std::vector<double>, you can't bind a reference to the object. Instead, compiler creates a temporary double object and bind the reference to said temporary. Because of that, you avoid ODR-using the static member of the class, as creating a double temporary doesn't ODR-use it, and ODR-using temporary is fine.

Fixing the issue is trivial if you have .cpp file for the class, in which case you simply define your static there. However, for header-only class the solution is not trivial until C++17, where you can have inline variables and have a very nice solution:

#include <vector>

class aClass
{
public:
    static const int HALLO;
};

inline const int aClass::HALLO = -3;

int main()
{
  std::vector<int> b;
  b.resize(10,aClass::HALLO); //fine

  return 0;
}
SergeyA
  • 61,605
  • 5
  • 78
  • 137