Question

I have been playing around with template classes and constants in gcc 4.8.2 and came across an interesting linker error:

#include <iostream>
using namespace std;

template <class T, int m>
class A {
public:
  static const T param = m;
  T value;

  A(const T &value, const T &dummy = T()) : value(value) {}
};

// Works with this
// template <class T, int m>
// const T A<T, m>::param = m;    

template <class T, int m>
A<T, m> operator +(const A<T, m> &a, const A<T, m> &b) {
  if (a.param != b.param) exit(1);

  // Works if I replace a.param with 0
  return A<T, m>(a.value + b.value, a.param);
}

int main() {
  A<int, 2> v = A<int, 2>(1) + A<int, 2>(2);
  cout << v.value << endl;
  return 0;
}

Compiling the code in the current state gives a linker error, telling me that A::param is not defined.

Trying this code in Visual Studio 2008 as well, it compiles and links without any problems. On gcc, it compiles when I either use the external declaration of the param constant, or if I replace a.param with 0 or nothing on the line indicated.

What I do not understand is why the line containing the if statement can use a.param without compilation error, while I cannot pass a.param to the constructor without declaring it externally.

So my question is: When do I need to declare param externally, and what is the difference between the access in the if statement and the constructor call? Which of the two compilers I tested does the "right" thing here, and which one has extended the standard?

Playing around a little more, I realized it also works when I specify the -O2 flag to g++.

Was it helpful?

Solution

template <class T, int m>
class A {
public:
  static const T param = m;
  T value;

  A(const T &value, const T &dummy = T()) : value(value) {}
};

// Works with this
// template <class T, int m>
// const T A<T, m>::param = m;

//gotta define it here!
template <class T, int m>
const T A<T, m>::param;

template <class T, int m>
A<T, m> operator +(const A<T, m> &a, const A<T, m> &b) {
  if (a.param != b.param) exit(1);

  // Works if I replace a.param with 0
  return A<T, m>(a.value + b.value, a.param);
}

int main() {
  A<int, 2> v = A<int, 2>(1) + A<int, 2>(2);
  std::cout << v.value << std::endl;
  return 0;
}

from here

If a static data member is of const integral or const enumeration type, you may specify a constant initializer in the static data member's declaration. This constant initializer must be an integral constant expression. Note that the constant initializer is not a definition. You still need to define the static member in an enclosing namespace.

In the cases that is working, the compilers are being non-compliant. Microsoft is non-compliant cuz Microsoft is almost never compliant, the g++ with -O2 is kinda funny, but in either case you've got no definition!

Edit: As a rule of thumb, I always define my static constant member variables outside of the class because then I never have to remember it. In this case, class A would be invalid if T was anything other than an integral type, such as a float. So rather than do any patterns around the exception, I tend to just stick with my patterns around the rule.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top