2017-04-08 1 views
2

J'utilise C++ 11 et j'essayais de créer une classe générique Handle dans mon application, où il est parfois possible de convertir des poignées avec différents types sous-jacents, mais seulement si les types sous-jacents sont liés comme ancêtre/descendant, sinon les tentatives de conversion devraient simplement échouer. J'ai également besoin d'une fonction qui ne manquera jamais qui me dit si une conversion est même possible entre les deux types. En particulier, je ne veux pas que le type sous-jacent tente d'effectuer une conversion en types qui ne sont pas dans ses propres lignes ascendantes/descendantes, donc je pensais si je définissais un foncteur basé sur un booléen qui me disait au moment de la compilation si le Les types étaient liés et utilisaient la spécialisation de modèle pour rejeter la conversion s'ils n'étaient pas liés, ou pour transmettre la demande de conversion au type sous-jacent s'ils étaient liés. Chaque classe de base contient une fonction de conversion basée sur un modèle qui sait comment convertir chacun des types correspondants dans la hiérarchie ainsi qu'une fonction booléenne modélisée qui indique si une telle conversion est possible en fonction des états internes de l'instance de classe.Choix de la fonction d'appel à l'aide de modèles

Ce que je mets ensemble ressemble à ceci:

template<class T> 
class MyHandle { 
public: 
    ... 
    template<bool> struct can_be_ref {  
     template<class U> bool operator()(const MyHandle *, const U*) const 
     { 
     } 
    }; 

    template<bool> struct as_ref { 
     template<class U> MyHandle<U> operator()(const MyHandle *, const U*) const 
     { 
      throw std::runtime_error("Illegal type conversion"); 
     } 
    }; 
    template<class U> bool can_be(); 
    template<class U> MyHandle<U> as(); 
private: 
    const T* get_member_reference() const; 
}; 

template<class T> struct MyHandle<T>::can_be_ref<true> {  
    template<class U> bool operator()(const MyHandle<T> *ptr, const U*) 
    { 
     ptr->get_member_reference()->can_be<U>(); 
    } 
}; 

template<class T> struct MyHandle<T>::as_ref<true> {  
    template<class U> MyHandle<U> operator()(const MyHandle<T> *ptr, const U*) const 
    { 
     return ptr->get_member_reference()->as<U>(); 
    } 
}; 

template<class T> template<class U> bool MyHandle<T>::can_be() 
{ 
    return can_be_ref < std::is_base_of<T, U>::value || std::is_base_of<U, T>::value >()(this, reinterpret_cast<const U *> (nullptr)); 
} 

template<class T> template<class U> MyHandle<U> MyHandle<T>::as() 
{ 
    return as_ref < std::is_base_of<T, U>::value || std::is_base_of<U, T>::value >()(this, reinterpret_cast<const U *> (nullptr)); 
} 

Cela ne compile pas, cependant, et je ne sais pas ce que je fais mal. L'échec se produit lorsque j'essaie de spécialiser les structures can_be_ref et as_ref, où le compilateur se plaint de trop peu de listes de paramètres de modèle. J'espère que ce que je veux faire est clair entre l'explication que j'ai proposée et ce fragment de code qui regrette ne fonctionne pas, mais c'est le seul moyen de décrire ce que j'essaie de faire . Qu'est-ce que je fais mal?

EDIT:

Précision, dire que j'ai Hiérarchies de classe suivantes:

class A { 
public: 
    template<class U> bool can_be(); 
    template<class U> MyHandle<U> as(); 
... 
}; 

class B : public A{ 
... 
}; 

class C { 
public: 
    template<class U> bool can_be(); 
    template<class U> MyHandle<U> as(); 
... 
}; 

Chaque hiérarchie a une méthode can_be et as définie qui ne concerne que lui-même avec les éléments de sa hiérarchie, et en Cela peut entraîner une erreur de compilation dans certains cas si l'argument du modèle n'est pas du bon type, ce qui explique pourquoi le type doit être vérifié lors de la compilation.
Et supposons que nous avons les variables suivantes définies:

MyHandle<A> a; 
MyHandle<B> b; 
MyHandle<C> c; 

Parce que a et b sont des types connexes, A::can_be et A::as peuvent être librement utilisés entre eux, mais un :: can_be peut produire une erreur de compilation. Par conséquent, le wrapper qui les entoure dans MyHandle masque cela afin que MyHandle<A>::can_be<C>() aways renvoie false, par exemple. Alors que MyHandle<B>::as<C>() lèverait toujours une exception, même en essayant de générer un appel à B::as<C> car cela pourrait entraîner une erreur de compilation.

Edit:

suggestion de Per Kamil ci-dessous, la solution était de migate la définition du modèle à la classe environnante.Ce que je faisais était de créer un modèle d'aide comme suit:

template<class T,class U,bool> class MyHandleConverter 
{ 
public: 
    inline MyHandleConverter(const MyHandle<T> *) { } 
    inline bool can_be() const { return false; } 
    inline MyHandle<U> as() const { return MyHandle<U>(nullptr); } 
}; 

j'ai décidé de renoncer à lancer des exceptions sur les conversions non valides, et maintenant chaque instance de MyHandle contient un pointeur vide appelé value qui peut contenir un pointeur vers plus d'informations sur le type réel sous-jacent, qui est nullptr si elle est invalide, et donc je peut alors créer une spécialisation partielle pour la MyHandleConverterClass comme suit:

template<class T,class U> class MyHandleConverter<T,U,true> { 
public: 
    inline MyHandleConverter(const MyHandle<T> *ref):reference(ref) { }  
    inline bool can_be() const { 
     if (std::is_base_of<T,U>::value) { 
      return true; 
     } else if (reference->value == nullptr) { 
      return false; 
     } else { 
      return reference->underlying_can_be((const U*)(nullptr)); 
     } 
    } 
    inline MyHandle<U> as() const { 
     if (std::is_base_of<U,T>::value) { 
      return MyHandle<U>(reference->value); 
     } else if (reference->value == nullptr) { 
      return MyHandle<U>(nullptr); 
     } else { 
      return reference->underlying_as((const U*)(nullptr)); 
     } 
    } 
private: 
    const MyHandle<T> *reference;  
}; 

au lieu de jeter des exceptions comme je l'ai fait précédemment, je retourne à la place un MyHandle invalide (qui a un constructeur spécial, MyHandle(nullptr_t), et l'état de MyHandle peut être interrogé par une simple méthode booléenne is_valid(), (et une exception éventuellement lancée par l'appelant si cela est souhaité, ce qui à mes fins revient à devoir écrire moins de try .... catch blocks que si j'avais la fonction as<U> jeter l'exception elle-même en cas d'échec).

La classe MyHandle a une méthode underlying_can_be et basé sur un modèle basé sur un modèle méthode underlying_as, qui simplement transmettre sa demande à can_be ou as méthodes du type de classe sous-jacente, respectivement. Il convient de noter que ces méthodes ne seraient même pas générés par le compilateur si elle n'invoquée par la classe MyHandleConverter<T,U,true>, maintenant les MyHandle can_be et as méthodes sont écrites ainsi:

template <class T> template<class U> bool MyHandle<T>::can_be() const { 
    return MyHandleConverter<T, U, are_related_handle_types<U,T>()>(this).can_be(); 
} 

template<class T> template<class U> MyHandle<U> MyHandle<T>::as() const { 
    return MyHandleConverter<T, U, are_handle_types_related<U,T>()>(this).as(); 
} 

are_handle_types_related est un constexpr basé sur un modèle function qui renvoie true si l'appel des types sous-jacents est suffisamment proche pour que l'appel du type sous-jacent aux méthodes can_be ou has de MyHandle n'entraîne pas d'erreur de compilation ou, dans certains cas, d'erreur logique qui ne peut pas être détectée lors de la compilation temps, ou même au moment de l'exécution sans écrire une logique de détection compliquée dans chaque type sous-jacent as et can_be, simplement en détectant que les deux classes sont chacune dérivées d'un type approprié pour que le processus de conversion réussisse de manière plausible.

De cette façon, quand les types sont incompatibles détectés par are_handle_types_related, et il serait incorrect d'appeler can_be du type correspondant ou as méthodes, l'instance de MyHandleConverter qui est créé est MyHandleConverter<T,U,false> qui ne tente pas d'appeler la classe sous-jacente type, alors que MyHandleConverter<T,U,true> le fait, mais ne sera instancié que pour les classes où il a déjà été jugé acceptable d'appeler de toute façon la fonction de conversion appropriée du type sous-jacent.

+0

Il est pas clair pour moi comment vous souhaitez les utiliser. Quels sont les usages valables? Quels sont certains des usages invalides? Les poster sera très utile. –

Répondre

2

Pour un modèle que vous spécialiser devez ajouter template mot-clé avant la spécialisation, comme:

template<class T> // Template parameter for 'MyHandle<T>' 
template<> // No unspecialized template parameters for 'can_be_ref', but indicate that it is a template anyway 
struct MyHandle<T>::can_be_ref<true> 
{  
    template<class U> bool operator()(const MyHandle<T> *ptr, const U*) 
    { 
     ptr->get_member_reference()->can_be<U>(); 
    } 
}; 

Toutefois, cela ne compile pas non plus. Selon http://en.cppreference.com/w/cpp/language/template_specialization:

membre ou un modèle de membre peut être imbriqué dans de nombreuses classe englobante de modèles. Dans une spécialisation explicite pour un tel membre, il existe un modèle <> pour chaque modèle de classe englobant explicitement .Dans une telle déclaration imbriquée, certains des niveaux peut restent non spécialisés (sauf qu'il ne peut pas se spécialiser un modèle membre de classe si sa classe englobante est non spécialisé)

nous ne pouvons pas se spécialiser complètement un modèle sans MyHandle se spécialise également. La solution peut être une spécialisation partielle des paramètres du modèle - paramètre de déplacement Ucan_be_ref::operator()-can_be_ref niveau:

template<class T> 
class MyHandle 
{ 
public: 
... 
    template<class U, bool> 
    struct can_be_ref 
    { 
     bool operator()(const MyHandle<T> *ptr, const U*) const 
     { 
      return false; 
     } 
    }; 

    template<class U, bool> 
    struct as_ref 
    { 
     MyHandle<U> operator()(const MyHandle<T> *, const U*) const 
     { 
      throw std::runtime_error("Illegal type conversion"); 
     } 
    }; 
... 
}; 

Ensuite, nous pouvons aller pour une spécialisation partielle:

template<class T> 
template<class U> 
struct MyHandle<T>::can_be_ref<U, true> 
{ 
    bool operator()(const MyHandle<T> * ptr, const U*) const 
    { 
     return ptr->get_member_reference()->can_be<U>(); 
    } 
}; 

template<class T> 
template<class U> 
struct MyHandle<T>::as_ref<U, true> 
{ 
    MyHandle<U> operator()(const MyHandle<T> *ptr, const U*) const 
    { 
     return ptr->get_member_reference()->as<U>(); 
    } 
}; 

template<class T> 
template<class U> bool MyHandle<T>::can_be() const 
{ 
    return can_be_ref<U, 
      std::is_base_of<T, U>::value || std::is_base_of<U, T>::value>()(
      this, nullptr); 
} 

template<class T> 
template<class U> MyHandle<U> MyHandle<T>::as() 
{ 
    return as_ref<U, 
      std::is_base_of<T, U>::value || std::is_base_of<U, T>::value>()(
      this, nullptr); 
} 

En fait, quand j'exécutai il par exemple des classes A , B, C s'est plaint sur la ligne return ptr->get_member_reference()->can_be<U>(); que: expected primary-expression before ')' token. Je ne comprends pas vraiment quel est le problème ici. L'appeler comme get_member_reference()->A::can_be<U>() travaillé. Une solution qui a fonctionné était de déterminer le paramètre U pour can_be<U>() par passage d 'arguments de type U:

class A { 
public: 
    template<class U> bool can_be(const U*) 
{ 
return can_be<U>(); 
} 
    template<class U> MyHandle<U> as(const U*) 
{ 
return as<U>(); 
} 

    template<class U> bool can_be(); 
    template<class U> MyHandle<U> as(); 
}; 

template<class T> 
    template<class U> 
    struct MyHandle<T>::can_be_ref<U, true> 
    { 
     bool operator()(const MyHandle<T> * ptr, const U* uptr) const 
     { 
      return ptr->get_member_reference()->can_be(uptr); 
     } 
    }; 

    template<class T> 
    template<class U> 
    struct MyHandle<T>::as_ref<U, true> 
    { 
     MyHandle<U> operator()(const MyHandle<T> *ptr, const U* uptr) const 
     { 
      return ptr->get_member_reference()->as(uptr); 
     } 
    };