1

I would like to get the templated type of the base class inside of an object that owns an instance of the derived class. The code snippet below won't work because Base and its ArbitraryType can't be referenced through DerivedString. (line marked with the exclamation point). However, it can most definitely be inferred from the type of its own template (OneOfTheDerivedTypes). In my case, I intend for AnotherObject with a defined template to be inherited from, so I don't want to just hardcode the return type to 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;
};

One solution to this is specify an additional template arg called ArbitraryType as seen below:

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;
};

The programmer then must specify both parameters where OneOfTheDerivedTypes is either DerivedFloat or DerivedString and ArbitraryType is float or string, respectively. Not a good solution because ArbitraryType is completely specified by the choice of OneOfTheDerivedTypes.

I think the extra template (ArbitraryType in AnotherObject) could be avoided by having Base return an instance of ArbitraryType in a public function (call it ReturnInstanceOfArbitraryType()) and use decltype(OneOfTheDerivedTypes::ReturnInstanceOfArbitraryType()) inside of AnotherObject. This seems inelegant because ReturnInstanceOfArbitraryType() is not useful otherwise (and must be public). Is this a case where the proper thing to do is to use a traits class? Is there a better solution? (Still getting the hang of some of this new C++11 stuff). Thanks!

Chet
  • 1,209
  • 1
  • 11
  • 29
  • 3
    Inside `Base` you can have `typedef ArbitratyType TypeOfBase` later on in the derived classes you can use `TypeOfBase` instead of `DerivedString::Base::ArbitraryType` in your first sample. – Sergei Kulik Jan 05 '16 at 05:48
  • Ah, yes. That works well, feel free to answer below. It insists that I use 'typename DerivedType::TypeOfBase', which is a bit unfortunate. Seems like it should be able to tell it's a type from the way that it's being used and from the fact that it's a typedef. I'm reading more about it here: http://stackoverflow.com/questions/7923369/when-is-the-typename-keyword-necessary – Chet Jan 05 '16 at 06:04
  • A using declaration can help clean up the typename keyword: using TypeOfBase = typename DerivedType::TypeOfBase – Chet Jan 05 '16 at 07:02

1 Answers1

0

Maybe I've misunderstood your question, but can't you just add a typedef to Base?

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

  using parameter_type = ArbitraryType;
};

And then you can refer to it:

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

And then the override in the derived type will enforce that the return types are the same (or covariant):

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

You could also add a static_assert if you want a more user-friendly error message:

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;
};

If you can't modify the Base template it's also possible to detect the type using some metaprogramming. First, declare (but don't define) a function that can deduce the type T from Base<T>:

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

Now define an alias template which takes one of the derived types as its template parameter, and uses the function above to find the template argument of its base-class:

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

This uses decltype to detect the return type of calling detect_base_parameter_type with a pointer to the derived type. That pointer will convert to a pointer to Base<T> (deducing whatever type T is for DerivedT) and the function's return type will be T*. Then we use remove_pointer to turn that into T.

Now you can use that alias template in your other classes:

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;
};
Jonathan Wakely
  • 166,810
  • 27
  • 341
  • 521