Получить шаблонный тип Base внутри класса, которому принадлежит экземпляр Derived

Я хотел бы получить шаблонный тип базового класса внутри объекта, которому принадлежит экземпляр производного класса. Приведенный ниже фрагмент кода не будет работать, поскольку на Base и его ArbitraryType нельзя ссылаться через DerivedString. (строка отмечена восклицательным знаком). Однако его можно определенно вывести из типа собственного шаблона (OneOfTheDerivedTypes). В моем случае я намерен наследовать AnotherObject с определенным шаблоном, поэтому я не хочу просто жестко кодировать тип возвращаемого значения для GetSomethingFromThingy().

// -------------------
// Provided by my API:
// -------------------
template <typename ArbitraryType>
class Base {
  virtual void DoSomething(ArbitraryType);
};

template <typename OneOfTheDerivedTypes>
class AnotherObject<OneOfTheDerivedTypes> {
  // Other functions that perform useful tasks are here so that
  // classes that inherit from AnotherObject need not implement them.
  void SomethingMagical();

  // A function with a specific return type.
  virtual DerivedString::Base::ArbitraryType GetSomethingFromThingy() = 0; /* ! */
 protected:
  OneOfTheDerivedTypes thingy;
};

// --------------------------------------
// Someone using my API would make these:
// --------------------------------------
class DerivedFloat : public Base<float> {
  void DoSomething(float) override;
};

class DerivedString : public Base<string> {
  void DoSomething(string) override;
};

class UsefulObject : public AnotherObject<DerivedString> {
  // Knows the required return type of GetSomethingFromThingy() without
  // needing to specify it as a template. Should throw compile-time error 
  // if you tried to override with a different return type. In other words,
  // forces return type to be string because of use of DerivedString.
  string GetSomethingFromThingy() override;
};

Одним из решений этой проблемы является указание дополнительного аргумента шаблона с именем ArbitraryType, как показано ниже:

template <typename OneOfTheDerivedTypes, typename ArbitraryType>
class AnotherObject<OneOfTheDerivedTypes> {
  virtual ArbitraryType GetSomethingFromThingy() = 0;
 protected:
  OneOfTheDerivedTypes thingy;
};

class UsefulObject<DerivedString, string> : public AnotherObject<DerivedString, string> {
  string GetSomethingFromThingy() override;
};

Затем программист должен указать оба параметра, где OneOfTheDerivedTypes — либо DerivedFloat, либо DerivedString, а ArbitraryType — float или string соответственно. Не очень хорошее решение, поскольку ArbitraryType полностью определяется выбором OneOfTheDerivedTypes.

Я думаю, что дополнительного шаблона (ArbitraryType в AnotherObject) можно было бы избежать, если бы Base возвращал экземпляр ArbitraryType в общедоступной функции (назовем ее ReturnInstanceOfArbitraryType()) и использовал decltype(OneOfTheDerivedTypes::ReturnInstanceOfArbitraryType()) внутри AnotherObject. Это кажется неэлегантным, потому что ReturnInstanceOfArbitraryType() в противном случае бесполезен (и должен быть общедоступным). Является ли это тем случаем, когда правильно использовать класс свойств? Есть ли лучшее решение? (Все еще разбираюсь с некоторыми из этих новых вещей С++ 11). Спасибо!


person Chet    schedule 05.01.2016    source источник
comment
Внутри Base вы можете иметь typedef ArbitratyType TypeOfBase позже, в производных классах вы можете использовать TypeOfBase вместо DerivedString::Base::ArbitraryType в первом примере.   -  person Sergei Kulik    schedule 05.01.2016
comment
О да. Это работает хорошо, не стесняйтесь отвечать ниже. Он настаивает на том, чтобы я использовал 'typename DerivedType::TypeOfBase', что немного неудачно. Похоже, он должен быть в состоянии сказать, что это тип по способу его использования и по тому факту, что это typedef. Я читаю больше об этом здесь: stackoverflow .com/questions/7923369/   -  person Chet    schedule 05.01.2016
comment
Объявление using может помочь очистить ключевое слово typename: using TypeOfBase = typename DerivedType::TypeOfBase   -  person Chet    schedule 05.01.2016


Ответы (1)


Возможно, я неправильно понял ваш вопрос, но вы не могли бы просто добавить typedef к Base?

template <typename ArbitraryType>
class Base {
  virtual void DoSomething(ArbitraryType);

  using parameter_type = ArbitraryType;
};

И тогда вы можете обратиться к нему:

template <typename OneOfTheDerivedTypes>
class AnotherObject {
  // A function with a specific return type.
  virtual typename OneOfTheDerivedTypes::parameter_type GetSomethingFromThingy() = 0;
};

И тогда override в производном типе обеспечит, чтобы возвращаемые типы были одинаковыми (или ковариантными):

class UsefulObject : public AnotherObject<DerivedString> {
  string GetSomethingFromThingy() override;
};

Вы также можете добавить static_assert, если хотите получить более удобное сообщение об ошибке:

class UsefulObject : public AnotherObject<DerivedString> {
  using base_parameter_type = typename AnotherObject<DerivedString>::parameter_type;
  static_assert(std::is_same<string, base_parameter_type>::value,
                "mismatched parameter types");

  string GetSomethingFromThingy() override;
};

Если вы не можете изменить шаблон Base, также возможно определить тип с помощью некоторого метапрограммирования. Во-первых, объявите (но не определяйте) функцию, которая может вывести тип T из Base<T>:

template<typename T> T* detect_base_parameter_type(Base<T>*);  // undefined

Теперь определите шаблон псевдонима, который принимает один из производных типов в качестве параметра шаблона и использует приведенную выше функцию для поиска аргумента шаблона своего базового класса:

template<typename DerivedT>
  using base_parameter_t = typename std::remove_pointer<
    decltype( detect_base_parameter_type(std::declval<DerivedT*>()) )
  >::type;

Здесь используется decltype для определения возвращаемого типа вызова detect_base_parameter_type с указателем на производный тип. Этот указатель будет преобразован в указатель на Base<T> (вывод любого типа T для DerivedT), а возвращаемый тип функции будет T*. Затем мы используем remove_pointer, чтобы превратить это в T.

Теперь вы можете использовать этот шаблон псевдонима в других своих классах:

template <typename OneOfTheDerivedTypes>
class AnotherObject {
  // A function with a specific return type.
  virtual base_parameter_t<OneOfTheDerivedTypes> GetSomethingFromThingy() = 0;
};

class UsefulObject : public AnotherObject<DerivedString> {
  using base_parameter_type = base_parameter_t<DerivedString>;
  static_assert(std::is_same<string, base_parameter_type>::value,
                "mismatched parameter types");

  string GetSomethingFromThingy() override;
};
person Jonathan Wakely    schedule 17.02.2017