Can C++ templates provide the common parent class of N given classes?
Asked Answered
W

2

3

I am looking for a c++ template which finds the common parent of a set of given classes.

For example

class Animal { ... };
class Mammal : public Animal { ... };
class Fish   : public Animal { ... };
class Cat    : public Mammal { ... };
class Dog    : public Mammal { ... };

std::unique_ptr<common_ancestor_of<Cat,Dog>::type> a = new Cat();
std::unique_ptr<common_ancestor_of<Cat,Dog>::type> b = new Dog();
std::unique_ptr<common_ancestor_of<Cat,Dog>::type> c = new Fish(); // compile error
std::unique_ptr<common_ancestor_of<Cat,Dog,Fish>::type> d = new Fish();

a and b are both std::unique_ptr<Mammal>, c is std::unique_ptr<Animal>.

How is this possible with modern C++?

Wigwam answered 7/1, 2021 at 20:24 Comment(8)
yes............Breather
Perhaps - en.cppreference.com/w/cpp/types/common_typeBrumfield
@Breather how? I am not aware of any way of finding this.Shirashirah
@RichardCritten no, std::common_type cannot be used to find base classes.Shirashirah
No this is not possible. There is no way to conjure up typename X from typename Y without having that information stored somewhere in the meta function.Individually
Probably not possible in the generic case, but could be possible under certain restrictions. Do you always have only a single base per class? Can you modify the bases?Stringency
There might not be a unique answerDowie
A bigger question is: why do you want this? If you don't know the exact base type, you don't know the interface. Thus you don't know how to infer functions or access pubic variables of it. And if you do know the common base type, you could as well explicitly specify it. Maybe this is a XY-problem: what are you trying to achieve?Breather
S
2

Afaik no, there is no way in C++ to get the base class of a given class. There is introspection (part of reflection) in the works, but I wouldn't hold my breath for it to go into the standard any time soon.

The only way would be to make the classes cooperate. E.g. Have each class a member alias using Base = Animal. And then cook a trait that finds the common base between them. That would be a lot of work. You need to take into account multiple base classes and chains of inheritance. It's not trivial. You need to analyze your problem and see if all this complicated work is worth it or if there is another simpler way for what you are trying to achieve (which btw you din't mention). You may have an XY problem on your hand.

Shirashirah answered 7/1, 2021 at 20:42 Comment(1)
You could use TR2Breather
W
1

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.

Warhead answered 7/1, 2021 at 21:11 Comment(2)
Any specific reason you're using long for the false overload of in_inheritance_chain?Breather
@Breather The 0 passed as an argument is an exact match for int, but also matches long, so will only call the long overload if can't match any int overload. That prevents me from writing a has_super traitWarhead

© 2022 - 2024 — McMap. All rights reserved.