1

I'm building a container type and attempting to reuse as much code as possible using the Curiously Recurring Template Pattern.

Here's the base code, which compiles in some cases, but not in others:

template<typename T, size_t N, typename ConcreteClass> struct _BaseClassOperators {
  const ConcreteClass& operator+=(const ConcreteClass& other) {
    ConcreteClass& self = static_cast<ConcreteClass&>(*this);
    for(size_t i = 0; i < N; ++i) {
      self.data[i] += other.data[i];
    }
    return self;
  }

  const ConcreteClass& operator+=(T value) {
    ConcreteClass& self = static_cast<ConcreteClass&>(*this);
    for(size_t i = 0; i < N; ++i) {
      self.data[i] += value;
    }
    return self;
  }

  friend ConcreteClass operator+(ConcreteClass lhs, const ConcreteClass& rhs) {
    lhs += rhs;
    return lhs;
  }

  friend ConcreteClass operator+(ConcreteClass lhs, T value) {
    lhs += value;
    return lhs;
  }

  friend ConcreteClass operator+(T value, ConcreteClass rhs) {
    rhs += value;
    return rhs;
  }
};

template<typename T, size_t N, typename ConcreteClass> struct _BaseClass : public _BaseClassOperators<T, N, ConcreteClass> {
};

template<typename T, typename ConcreteClass> struct _BaseClass<T, 3, ConcreteClass> : public _BaseClassOperators<T, 3, ConcreteClass> {
  constexpr _BaseClass() : data { 0, 0, 0 } {}
  constexpr _BaseClass(T first, T second, T third) : data { first, second, third } {}
  explicit constexpr _BaseClass(T value) : data { value, value, value } {}

  static constexpr ConcreteClass SomeInstance = ConcreteClass(ConcreteClass::SomeConstant, ConcreteClass::SomeConstant, ConcreteClass::SomeConstant);

  T data[3];
};

template<typename T, size_t N> struct DerivedClass : public _BaseClass<T, N, DerivedClass<T, N>> {
  using _BaseClass<T, N, DerivedClass>::_BaseClass;
  static constexpr T SomeConstant = 0;
};

template<> struct DerivedClass<float, 3> : public _BaseClass<float, 3, DerivedClass<float, 3>> {
  using _BaseClass::_BaseClass;
  static constexpr float SomeConstant = 5.0f;
};

typedef DerivedClass<float, 3> DC3;
typedef DerivedClass<uint8_t, 3> DCI3;

When I used the + operator taking two DC3 instances, everything compiled with no problem:

DC3 do_something() {
  return DC3::SomeInstance + DC3::SomeInstance;
}

int main(int argc, char* argv[]) {
  do_something();
  return 0;
}

When I changed do_something to call the + operator with DC3 and float, it complains:

DC3 do_something() {
  return DC3::SomeInstance + 0.5f;
}

line number of do_something()...: undefined reference to `_BaseClass >::SomeInstance'

What is causing SomeInstance to be undefined in this case?

I also get the same error if I define an overloaded operator[] on the operators class, and use it to implement operator+=(const DerivedClass& other), changing self.data[i] to self[i].

I also notice that if I define SomeInstance as const instead of constexpr, and initialize it (in a templated way) just after the template body, then I don't have any problems.

In case this is a bug, I'm using avr-g++ (gcc) 4.8.1 on OSX, with -std=gnu++11 defined.

Merlyn Morgan-Graham
  • 58,163
  • 16
  • 128
  • 183
  • There are several similar questions in some form or another, but they all reference a `constexpr` *constant* in the derived class. Even if I use the empty constructor of `DerivedClass()` instead, and avoid referencing the constants, I still have the problem. Is that illegal, too? – Merlyn Morgan-Graham Jul 31 '16 at 05:36
  • http://stackoverflow.com/questions/27443063/initializing-base-class-static-const-variable-with-derived-class-variable-via-c http://stackoverflow.com/questions/35753956/crtp-compiling-error/35754443 http://stackoverflow.com/questions/37816186/initializing-a-static-constexpr-data-member-of-the-base-class-by-using-a-static http://stackoverflow.com/questions/35854686/curiously-recurring-template-pattern-crtp-with-static-constexpr-in-clang – Merlyn Morgan-Graham Jul 31 '16 at 05:36
  • Note that I'm getting the same error for your first case as well, which makes sense. The overloaded operators odr-use `DC3::SomeInstance`, so it needs a definition (the one in the class definition is just a declaration before C++17, details and standard quotes in one of the questions you linked). Adding `template constexpr C _BaseClass::SomeInstance;` at namespace scope will make the code compile. Note that the `constexpr` in that definition should really be `const`, but GCC [incorrectly rejects that](https://gcc.gnu.org/bugzilla/show_bug.cgi?id=58541). – bogdan Jul 31 '16 at 10:07
  • 1
    Note that, as explained in the linked answer, the code is still invalid, and incorrectly accepted by GCC. It will still be invalid even in C++17, because then the in-class declaration will be a definition, and you can't define an object of an incomplete type - `ConcreteClass` refers to a class whose definition hasn't been seen yet when `_BaseClass>` is instantiated. – bogdan Jul 31 '16 at 10:24

0 Answers0