Вземете шаблонен тип 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 или низ. Не е добро решение, защото ArbitraryType е напълно специфициран чрез избора на OneOfTheDerivedTypes.

Мисля, че допълнителният шаблон (ArbitraryType в AnotherObject) може да бъде избегнат, като Base върне екземпляр на ArbitraryType в публична функция (наречете го ReturnInstanceOfArbitraryType()) и използвайте decltype(OneOfTheDerivedTypes::ReturnInstanceOfArbitraryType()) вътре в AnotherObject. Това изглежда неелегантно, защото ReturnInstanceOfArbitraryType() не е полезен иначе (и трябва да бъде публичен). Това случай ли е, когато правилното нещо, което трябва да направите, е да използвате клас черти? Има ли по-добро решение? (Все още разбирам някои от тези нови неща на C++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
Използването на декларация може да помогне за изчистване на ключовата дума 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