No, common_ancestor_of
would require reflection to get the base class of any of the given classes.
If you have a concrete set of classes, you could use std::variant
of all the possible classes.
If not, you could use a base class trait (Either a struct base_class
that you specialise for all of your types or a member type like using super = ...
or using base = ...
) and manually find the common ancestor:
template<typename T>
struct type_identity {
using type = T;
};
template<typename... Types>
struct common_ancestor_of;
template<typename T>
struct common_ancestor_of<T> {
using type = T;
};
template<typename T>
struct common_ancestor_of<T, T> {
using type = T;
};
template<typename T, typename U, typename... Rest>
struct common_ancestor_of<T, U, Rest...> : common_ancestor_of<typename common_ancestor_of<T, U>::type, Rest...> {};
template<typename T, typename U>
struct common_ancestor_of<T, U> {
private:
// Base == Derived, so is in it's inheritance chain
template<typename Base, typename Derived, typename std::enable_if<std::is_same<Base, Derived>::value, int>::type = 0>
static constexpr bool in_inheritance_chain(int) {
return true;
}
// Base != Derived, but Derived has a member type `super`, so recursively check `super`
template<typename Base, typename Derived, typename std::enable_if<!std::is_same<Base, Derived>::value && (noexcept(type_identity<typename Derived::super>{}), true), int>::type = 0>
static constexpr bool in_inheritance_chain(int) {
return in_inheritance_chain<Base, typename Derived::super>(0);
}
// Base != Derived and Derived doesn't have a member type `super`, so it isn't in the inheritance chain
template<typename Base, typename Derived>
static constexpr bool in_inheritance_chain(long) {
return false;
}
// T1 is in the inheritance chain for U1, so it is the common ancestor
template<typename T1, typename U1, typename std::enable_if<in_inheritance_chain<T1, U1>(0), int>::type = 0>
static type_identity<T1> find_common_ancestor(int);
// T1 is not in the inheritance chain, so check T1::super
template<typename T1, typename U1>
static decltype(find_common_ancestor<typename T1::super, U1>(0)) find_common_ancestor(long) {}
public:
using type = typename decltype(find_common_ancestor<T, U>(0))::type;
};
template<typename... Types>
using common_ancestor_of_t = typename common_ancestor_of<Types...>::type;
class Animal { };
class Mammal : public Animal { public: using super = Animal; };
class Fish : public Animal { public: using super = Animal; };
class Cat : public Mammal { public: using super = Mammal; };
class Dog : public Mammal { public: using super = Mammal; };
static_assert(std::is_same<typename Cat::super::super, Animal>::value);
static_assert(std::is_same<common_ancestor_of_t<Cat, Dog>, Mammal>::value);
static_assert(std::is_same<common_ancestor_of_t<Cat, Fish>, Animal>::value);
static_assert(std::is_same<common_ancestor_of_t<Fish, Cat>, Animal>::value);
static_assert(std::is_same<common_ancestor_of_t<Cat, Dog, Fish>, Animal>::value);
This gets the most specialised common ancestor, but consider using the common ancestor you already have: std::unique_ptr<Animal>
. If you specifically write std::unique_ptr<common_ancestor_of_t<Cat, Dog>>
in your code, it is just as easy to write std::unique_ptr<Mammal>
. If it's behind some templated code, std::unique_ptr<Animal>
should work just as well.
std::common_type
cannot be used to find base classes. – Shirashirah