Проблема рефакторинга любопытно повторяющегося шаблона шаблона

Следующий код не компилируется на g++ 4.6.1:

template<class Base>
struct GetBase {
  Base * getBase() {
    return static_cast<Base *>(this);
  }
};

template<class Derived>
struct Parent : private GetBase<Derived> {
  using GetBase<Derived>::getBase;
  int y() {
    return getBase()->x();
  }
};

struct Child : public Parent<Child> {
  int x() {
    return 5;
  }
  int z() {
    return y();
  }
};

с ошибкой

In member function ‘Base* GetBase<Base>::getBase() [with Base = Child]’:
    instantiated from ‘int Parent<Derived>::y() [with Derived = Child]’
    instantiated from here
error: ‘GetBase<Child>’ is an inaccessible base of ‘Child’

Изменение static_cast на reinterpret_cast приведет к компиляции кода, и в этом случае он будет работать, но мне интересно, является ли это приемлемым решением во всех случаях? т. е. бывает ли так, что указатель на базовый класс не совпадает с этим? Я предполагаю, что при множественном наследовании это может произойти, если у родителей есть элементы данных? Если GetBase является первым суперклассом, гарантируется ли равенство указателей this?


person user965349    schedule 23.01.2012    source источник
comment
Я в шоке, это компилируется на VS2010 ... независимо от того, вам, вероятно, следует реорганизовать свой дизайн.   -  person AJG85    schedule 24.01.2012
comment
Да, бывают случаи, когда reinterpret_cast‹› не работает, и большинство из них полностью определяются реализацией.   -  person Martin York    schedule 24.01.2012
comment
По крайней мере, рассмотрите возможность удаления бесконечных уровней getBase() и предпочтите dynamic_cast для определения производного от указателя базового класса.   -  person AJG85    schedule 24.01.2012
comment
К вашему сведению, виртуальные методы, вероятно, могли бы упростить эту сумасшедшую схему наследования.   -  person AJG85    schedule 24.01.2012
comment
@ AJG85- Весь смысл этой настройки (CRTP) заключается в том, чтобы избежать динамического полиморфизма и обрабатывать все во время компиляции. Добавление виртуальных методов и dynamic_cast противоречит цели дизайна.   -  person templatetypedef    schedule 24.01.2012
comment
@templatetypedef Это желательно? Я полагаю, что стоимость может быть меньше, но программисты обычно значительно дороже, чем оборудование.   -  person AJG85    schedule 24.01.2012
comment
Имена GetBase, getBase и Base вводят в заблуждение, потому что на самом деле вы конвертируете указатель в производный тип.   -  person Alexey Kukanov    schedule 24.01.2012


Ответы (2)


Хороший вопрос; это заставило меня узнать что-то новое о static_cast.

Я думаю, что следующий код достигает того, чего вы хотите: предоставляет базовый класс для CRTP с функцией-членом, которая приводит this к производному типу, но делает его доступным только для прямых потомков этого базового класса. Он компилируется с помощью GCC 4.3.4 (проверено на ideone) и Clang (проверено на llvm.org). Извините, я не мог удержаться от изменения имен, которые я нахожу сбивающими с толку.

#include <iostream>

template<class Derived>
class CRTP {
protected:
  Derived * derived_this() {
    return static_cast<Derived *>(this);
  }
};

template<class Derived>
struct Parent : public CRTP<Derived> {
private:
  using CRTP<Derived>::derived_this;
public:
  int y() {
    return derived_this()->x();
  }
};

struct Child : public Parent<Child> {
  int x() {
    return 5;
  }
  int z() {
    return y();
  }
};

int main() {
  std::cout << Child().z() << std::endl;
  return 0;
}

Этот вариант работает, потому что наследование является общедоступным, что позволяет стандартное преобразование указателя на производный класс в указатель на базовый класс, а также обратное преобразование (из базового в производный) с помощью static_cast, которое CRTP потребности. Частное наследование в вашем коде запрещает это. Поэтому я сделал наследование общедоступным, изменил метод, чтобы он был защищен, и в Parent еще больше ограничил доступ, поместив объявление using в приватный раздел.

person Alexey Kukanov    schedule 23.01.2012
comment
+1 и спасибо за этот ответ. Это спасло меня от отладки. MSVC++ 2012 Express по-прежнему компилируется с закрытым наследованием... - person TemplateRex; 30.10.2012

Мне интересно, является ли это приемлемым решением во всех случаях?

Нет (см. ниже)

было ли когда-нибудь так, что указатель на базовый класс не совпадает с этим?

Да

  • При множественном наследовании нельзя ожидать, что базовые классы будут иметь один и тот же адрес.

  • В зависимости от компилятора производный класс с указателем на виртуальную таблицу может не иметь того же this, что и базовый класс, не имеющий указателя на виртуальную таблицу.

При явном восходящем приведении к базе static_cast является подходящим приведением C++.

person Drew Dormann    schedule 23.01.2012