Ошибка при объявлении статического экземпляра constexpr производного класса в базовом классе CRTP

Я создаю тип контейнера и пытаюсь повторно использовать как можно больше кода, используя Удивительно повторяющийся шаблон шаблона.

Вот базовый код, который в некоторых случаях компилируется, а в других нет:

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;

Когда я использовал оператор +, взяв два экземпляра DC3, все скомпилировалось без проблем:

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

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

Когда я изменил do_something на вызов оператора + с DC3 и float, он жалуется:

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

line number of do_something()...: неопределенная ссылка на `_BaseClass >::SomeInstance'

Почему в данном случае SomeInstance не определено?

Я также получаю ту же ошибку, если определяю перегруженный operator[] в классе операторов и использую его для реализации operator+=(const DerivedClass& other), изменяя self.data[i] на self[i].

Я также заметил, что если я определяю SomeInstance как const вместо constexpr и инициализирую его (по шаблону) сразу после тела шаблона, у меня не возникает никаких проблем.

В случае, если это ошибка, я использую avr-g++ (gcc) 4.8.1 на OSX с определенным -std=gnu++11.


person Merlyn Morgan-Graham    schedule 31.07.2016    source источник
comment
Есть несколько похожих вопросов в той или иной форме, но все они ссылаются на constexpr константу в производном классе. Даже если вместо этого я использую пустой конструктор DerivedClass() и избегаю ссылок на константы, у меня все еще есть проблема. Это тоже незаконно?   -  person Merlyn Morgan-Graham    schedule 31.07.2016
comment
Обратите внимание, что я получаю ту же ошибку и для вашего первого случая, что имеет смысл. Перегруженные операторы odr-используют DC3::SomeInstance, поэтому ему нужно определение (тот, что в определении класса, - это просто объявление перед С++ 17, подробности и стандартные кавычки в одном из вопросов, которые вы связали). Добавление template<class T, class C> constexpr C _BaseClass<T, 3, C>::SomeInstance; в пространство имен приведет к компиляции кода. Обратите внимание, что constexpr в этом определении на самом деле должно быть const, но GCC ошибочно отклоняет это< /а>.   -  person bogdan    schedule 31.07.2016
comment
Обратите внимание, что, как объяснено в связанном ответе, код по-прежнему недействителен и неправильно принят GCC. Это все равно будет недействительным даже в C++17, потому что тогда объявление в классе будет определением, и вы не сможете определить объект неполного типа - ConcreteClass относится к классу, определение которого еще не видели когда создается экземпляр _BaseClass<float, 3, DerivedClass<float, 3>>.   -  person bogdan    schedule 31.07.2016