3 votes

Les modèles C++ peuvent-ils fournir la classe mère commune de N classes données ?

Je cherche un modèle c++ qui trouve le parent commun d'un ensemble de classes données.

Par exemple

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 y b sont tous deux std::unique_ptr<Mammal> , c est std::unique_ptr<Animal> .

Comment cela est-il possible avec le C++ moderne ?

1voto

bolov Points 4005

A ce jour, il n'existe aucun moyen en C++ d'obtenir la classe de base d'une classe donnée. L'introspection (qui fait partie de la réflexion) est en préparation, mais je ne m'attends pas à ce qu'elle soit intégrée à la norme de sitôt.

Le seul moyen serait de faire coopérer les classes. Par exemple, donner à chaque classe un alias de membre. using Base = Animal . Et ensuite cuisiner un trait qui trouve la base commune entre eux. Cela représenterait beaucoup de travail. Vous devez prendre en compte les classes de base multiples et les chaînes d'héritage. Ce n'est pas trivial. Vous devez analyser votre problème et voir si tout ce travail compliqué en vaut la peine ou s'il existe un autre moyen plus simple pour ce que vous essayez d'accomplir (que vous n'avez pas mentionné). Vous avez peut-être un XY problème sur votre main.

1voto

Artyer Points 3473

Non, common_ancestor_of nécessiterait une réflexion pour obtenir la classe de base de l'une des classes données.

Si vous avez un ensemble concret de classes, vous pouvez utiliser std::variant de toutes les classes possibles.

Sinon, vous pouvez utiliser un trait de classe de base (soit un struct base_class que vous spécialisez pour tous vos types ou un type membre comme using super = ... o using base = ... ) et trouver manuellement l'ancêtre commun :

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

Cela permet d'obtenir l'ancêtre commun le plus spécialisé, mais pensez à utiliser l'ancêtre commun que vous avez déjà : std::unique_ptr<Animal> . Si vous écrivez spécifiquement std::unique_ptr<common_ancestor_of_t<Cat, Dog>> dans votre code, il est tout aussi facile d'écrire std::unique_ptr<Mammal> . Si c'est derrière un code modèle, std::unique_ptr<Animal> devrait fonctionner tout aussi bien.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X