J'ai une classe Container
qui contient des objets dont le type peut être dérivé de n'importe quelle combinaison de certaines classes de base (TypeA
, TypeB
, etc.). La classe de base de Container
a des méthodes virtuelles qui renvoient un pointeur sur l'objet contenu; ceux-ci doivent renvoyer nullptr
si l'objet contenu n'est pas dérivé de la classe attendue. Je voudrais remplacer de manière sélective les méthodes de la base en fonction du paramètre de modèle de Container
. J'ai essayé d'utiliser SFINAE comme suit, mais il ne compile pas. Je voudrais éviter de spécialiser Container
pour chaque combinaison possible car il pourrait y en avoir beaucoup.
#include <type_traits>
#include <iostream>
using namespace std;
class TypeA {};
class TypeB {};
class TypeAB: public TypeA, public TypeB {};
struct Container_base {
virtual TypeA* get_TypeA() {return nullptr;}
virtual TypeB* get_TypeB() {return nullptr;}
};
template <typename T>
struct Container: public Container_base
{
Container(): ptr(new T()) {}
//Override only if T is derived from TypeA
auto get_TypeA() -> enable_if<is_base_of<TypeA, T>::value, TypeA*>::type
{return ptr;}
//Override only if T is dervied from TypeB
auto get_TypeB() -> enable_if<is_base_of<TypeB, T>::value, TypeB*>::type
{return ptr;}
private:
T* ptr;
};
int main(int argc, char *argv[])
{
Container<TypeA> typea;
Container<TypeB> typeb;
Container<TypeAB> typeab;
cout << typea.get_TypeA() << endl; //valid pointer
cout << typea.get_TypeB() << endl; //nullptr
cout << typeb.get_TypeA() << endl; //nullptr
cout << typeb.get_TypeB() << endl; //valid pointer
cout << typeab.get_TypeA() << endl; //valid pointer
cout << typeab.get_TypeB() << endl; //valid pointer
return 0;
}
... ou vous pouvez changer votre approche en une approche plus simple:
template <typename T>
struct Container: public Container_base
{
TypeA* get_TypeA() override
{
if constexpr(is_base_of_v<TypeA, T>)
return ptr;
else
return nullptr;
}
...
};
et comptez sur l'optimiseur pour lisser les rides. C'est comme remplacer plusieurs fonctions return nullptr
par une (en binaire final). Ou supprimer la branche morte du code si votre compilateur ne prend pas en charge if constexpr
.
Modifier:
... ou (si vous insistez pour utiliser SFINAE) quelque chose dans ce sens:
template<class B, class T, enable_if_t< is_base_of_v<B, T>>...> B* cast_impl(T* p) { return p; }
template<class B, class T, enable_if_t<!is_base_of_v<B, T>>...> B* cast_impl(T* p) { return nullptr; }
template <typename T>
struct Container: public Container_base
{
...
TypeA* get_TypeA() override { return cast_impl<TypeA>(ptr); }
TypeB* get_TypeB() override { return cast_impl<TypeB>(ptr); }
private:
T* ptr;
};
Le CRTP à la rescousse!
template<class T, class D, class Base, class=void>
struct Container_getA:Base {};
template<class T, class D, class Base, class=void>
struct Container_getB:Base {};
template<class T, class D, class Base>
struct Container_getA<T, D, Base, std::enable_if_t<std::is_base_of<TypeA,T>{}>>:
Base
{
TypeA* get_TypeA() final { return self()->ptr; }
D* self() { return static_cast<D*>(this); }
};
template<class T, class D, class Base>
struct Container_getB<T, D, Base, std::enable_if_t<std::is_base_of<TypeB,T>{}>>:
Base
{
TypeB* get_TypeB() final { return self()->ptr; }
D* self() { return static_cast<D*>(this); }
};
template <class T>
struct Container:
Container_getA< T, Container<T>,
Container_getB< T, Container<T>,
Container_base
>
>
{
Container(): ptr(new T()) {}
public: // either public, or complex friend declarations; just make it public
T* ptr;
};
et fait.
Vous pouvez faire un peu de travail pour permettre:
struct Container: Bases< T, Container<T>, Container_getA, Container_getB, Container_getC >
ou similaire où nous plions les bases CRTP en.
Vous pouvez également nettoyer votre syntaxe:
template<class...Ts>
struct types {};
template<class T>
struct tag_t {using type=T;};
template<class T>
constexpr tag_t<T> tag{};
Ensuite, au lieu d’avoir une pile de getters nommés, ayez:
template<class List>
struct Container_getters;
template<class T>
struct Container_get {
virtual T* get( tag_t<T> ) { return nullptr; }
};
template<class...Ts>
struct Container_getters<types<Ts...>>:
Container_get<Ts>...
{
using Container_get<Ts>::get...; // C++17
template<class T>
T* get() { return get(tag<T>); }
};
et maintenant une liste de types centrale peut être utilisée pour gérer l'ensemble des types que vous pouvez obtenir à partir du conteneur.
Nous pouvons ensuite utiliser cette liste de types centrale pour écrire les aides intermédiaires CRTP.
template<class Actual, class Derived, class Target, class Base, class=void>
struct Container_impl_get:Base {};
template<class Actual, class Derived, class Target, class Base>
struct Container_impl_get<Actual, Derived, Target, Base,
std::enable_if_t<std::is_base_of<Target, Actual>{}>
>:Base {
using Base::get;
virtual Target* get( tag_t<Target> ) final { return self()->ptr; }
Derived* self() { return static_cast<Derived*>(this); }
};
et maintenant nous avons juste besoin d'écrire les machines de pliage.
template<class Actual, class Derived, class List>
struct Container_get_folder;
template<class Actual, class Derived, class List>
using Container_get_folder_t=typename Container_get_folder<Actual, Derived, List>::type;
template<class Actual, class Derived>
struct Container_get_folder<Actual, Derived, types<>> {
using type=Container_base;
};
template<class Actual, class Derived, class T0, class...Ts>
struct Container_get_folder<Actual, Derived, types<T0, Ts...>> {
using type=Container_impl_get<Actual, Derived, T0,
Container_get_folder_t<Actual, Derived, types<Ts...>>
>;
};
alors nous obtenons
using Container_types = types<TypeA, TypeB, TypeC>;
struct Container_base:Container_getters<Container_types> {
};
template <typename T>
struct Container: Container_get_folder_t<T, Container<T>, Container_types>
{
Container(): ptr(new T()) {}
T* ptr;
};
et maintenant nous pouvons étendre cela en ajoutant simplement un type à Container_types
.
Les appelants qui souhaitent un type spécifique peuvent soit:
Container_base* ptr = /* whatever */;
ptr->get<TypeA>()
ou
ptr->get(tag<TypeA>);
les deux fonctionnent également bien.
Exemple en direct - il utilise une fonctionnalité C++ 14 ou deux (à savoir des modèles de variable dans tag
), mais vous pouvez remplacer tag<X>
par tag_t<X>{}
.
J'ai essayé d'utiliser SFINAE comme suit, mais il ne compile pas. Je voudrais éviter de spécialiser le conteneur pour chaque combinaison possible car il pourrait y en avoir beaucoup.
Malheureusement, les fonctions virtuelles et les fonctions de modèle sont incompatibles. Et vous ne pouvez pas utiliser SFINAE avec des méthodes non template, donc
auto get_TypeA()
-> typename std::enable_if<std::is_base_of<TypeA, T>::value, TypeA*>::type
{return ptr;}
ne fonctionne pas car le type T
est un argument de modèle de la classe, pas un argument de modèle de la méthode.
Pour activer SFINAE, vous pouvez gabarit la méthode comme suit
template <typename U = T>
auto get_TypeA()
-> typename std::enable_if<std::is_base_of<TypeA, U>::value, TypeA*>::type
{return ptr;}
et maintenant SFINAE fonctionne, mais get_TypeA()
est une méthode de modèle et ne peut donc plus être virtuelle.
Si vous avez vraiment besoin de fonctions virtuelles, vous pouvez résoudre le problème avec la spécialisation héritage et modèles (voir la réponse de Yakk).
Mais, si vous n'avez pas vraiment besoin que les fonctions get_TypeX()
soient virtuelles, je vous propose une solution complètement différente (et plus simple, je suppose), entièrement basée sur un couple (quel que soit le nombre de classes TypeX
) de méthodes template.
Je veux dire ... si vous écrivez quelques méthodes de modèle get_Type()
alternatives comme suit
template <typename U>
auto get_Type()
-> std::enable_if_t<true == std::is_base_of<U, T>::value, U*>
{ return ptr; }
template <typename U>
auto get_Type()
-> std::enable_if_t<false == std::is_base_of<U, T>::value, U*>
{ return nullptr; }
vous n'avez plus besoin de Container_base
et le type du pointeur demandé devient le paramètre de modèle de la méthode appelée comme suit
typea.get_Type<TypeA>()
Voici un exemple complet de C++ 14 (si vous avez besoin d’une solution C++ 11, utilisez simplement typename std::enable_if<>::type
au lieu de std::enable_if_t<>
).
#include <type_traits>
#include <iostream>
class TypeA {};
class TypeB {};
class TypeAB: public TypeA, public TypeB {};
template <typename T>
struct Container
{
private:
T* ptr;
public:
Container(): ptr{new T{}} {}
template <typename U>
auto get_Type()
-> std::enable_if_t<true == std::is_base_of<U, T>::value, U*>
{ return ptr; }
template <typename U>
auto get_Type()
-> std::enable_if_t<false == std::is_base_of<U, T>::value, U*>
{ return nullptr; }
};
int main ()
{
Container<TypeA> typea;
Container<TypeB> typeb;
Container<TypeAB> typeab;
std::cout << typea.get_Type<TypeA>() << std::endl; //valid pointer
std::cout << typea.get_Type<TypeB>() << std::endl; //nullptr
std::cout << typeb.get_Type<TypeA>() << std::endl; //nullptr
std::cout << typeb.get_Type<TypeB>() << std::endl; //valid pointer
std::cout << typeab.get_Type<TypeA>() << std::endl; //valid pointer
std::cout << typeab.get_Type<TypeB>() << std::endl; //valid pointer
}