2017-06-26 2 views
0

Je voudrais comprendre s'il est possible d'utiliser une classe imbriquée d'une classe CRTP feuille dans la classe CRTP de base. L'exemple ci-dessous illustre le problème.CRTP - visibilité du type d'une classe feuille imbriquée

#include <iostream> 

using namespace std; 

template<class T> 
class A 
{ 

protected: 
    T* asLeaf(void) 
     {return static_cast<T*>(this);} 
    T const* asLeaf(void) const 
     {return static_cast<T const*>(this);} 
public: 

    struct Inner 
    {int a = 10;}; 

    void dispInner(void) const 
     {std::cout << asLeaf()->inner.a << std::endl;} 

    // I would like to use T::Inner in this class, e.g. 
    // typename T::Inner mvInnerA; 
    // However, I understand that it is not possible to 
    // use it in the form that is stated above. Thus, 
    // I am looking for any possible workarounds. 

}; 


class B: public A<B> 
{ 
public: 
    struct Inner: public A<B>::Inner 
    {int b = 20;}; 

protected: 
    friend A<B>; 
    B::Inner inner; 

public: 
    void dispInner(void) const 
     { 
      A<B>::dispInner(); 
      std::cout << asLeaf()->inner.b << std::endl; 
     } 
}; 


int main() 
{ 

    B b; 
    b.dispInner(); 

    return 0; 

} 

EDIT

Je voudrais donner quelques commentaires supplémentaires sur la base des commentaires que je reçois:

  • Je suis conscient que je ne sois pas en utilisant des pratiques de conception adéquates . En particulier, on peut se demander si A devrait être au courant de l'existence de inner. Cependant, je voudrais définir un objet inner du type B::Inner dans A au lieu de fournir la définition de inner dans B et l'utiliser dans A.
  • Je suis conscient que je ne peux pas expédier déclarer B et/ou B::Inner et des raisons pour lesquelles cela ne peut pas être fait. Ainsi, techniquement, le problème de conception n'a pas de solution. Cependant, je suis à la recherche d'une solution de contournement réalisable.

Je l'ai déjà examiné plusieurs solutions alternatives:

  • L'une des solutions possibles possibles est de ne pas faire des tentatives pour « définir » B::Inner inner dans A et utiliser les fonctions membres de A pour fournir la fonctionnalité permet de modifier la partie A<B>::Inner de B::Inner inner.
  • Une autre solution possible consiste à définir explicitement les classes A<B>::Inner et B::Inner (c'est-à-dire pas comme des classes imbriquées). Cependant, je préférerais éviter cela, parce que, par sa conception, on ne prévoit pas que toutes les classes qui ne dérivent pas de A devront interagir avec A<B>::Inner ou les classes qui dérivent de A<B>::Inner

Les deux solutions que je ont présenté peut être acceptable. Cependant, je cherche des alternatives réalisables.

+0

La règle générale est que votre classe de base ne doit pas connaître son enfant, vous présentez des dépendances cycliques qui sont mauvaises – lapinozz

+1

@lapinozz c'est pourquoi il utilise le CRTP (https://en.wikipedia.org/wiki/Curiously_recurring_template_pattern) et rien de mal avec ça. –

+0

@lapinozz - Vous pensez en termes de polymorphisme dynamique, qui est complètement orthogonal au polymorphisme statique avec le CRTP. Les mêmes "règles" ne s'appliquent pas nécessairement. – StoryTeller

Répondre

1

La norme disent que:

Une classe est considérée comme un type d'objet complètement défini (ou un type complet) à la fermeture de la classe }-spécificateur.

Il en résulte que B n'est pas un objet complètement défini lorsque vous vous spécialisez A comme A<B>. Par conséquent, vous ne pouvez pas espérer pouvoir accéder à ses membres ou types ou quoi que ce soit à partir de la définition de A (même si vous pouvez rappeler la classe dérivée depuis la définition d'une méthode membre de A, ce qui est parfaitement légal au lieu d'autres que le but de l'idiome de CRTP).
Autrement dit, quand vous faites ceci:

typename T::Inner mvInnerA 

Vous avez aucune garantie que T est un objet complètement défini et qui est la raison pour laquelle vous obtenez l'erreur.


Quelques alternatives:

  1. Vous pouvez définir mvInnerType en fonction au lieu d'un type et l'utiliser comme une usine pour créer des objets de type T::inner:

    [static] auto mvInnerA() { 
        return typename T::Inner{}; 
    } 
    

    Utilisez-le comme:

    auto foo = A<B>::mvInnerA(); 
    

    Ou:

    auto foo = obj.mvInnerA(); 
    

    La bonne forme dépend du fait que vous le faites static ou non.
    Notez que vous pouvez toujours utiliser le type caché en quelque sorte, même si son nom n'est pas accessible:

    using HiddenType = decltype(A<B>::mvInnerA()); 
    HiddenType foo = A<B>::mvInnerA(); 
    
  2. Vous pouvez définir mvInnerA en utilisant un modèle d'une déclaration d'alias comme ceci:

    template<typename U = T> 
    using mvInnerA = typename U::Inner; 
    

    utilisez ensuite comme:

    auto foo = A<B>::mvInnerA<>{}; 
    

    Pour le type T est (laissez-moi vous dire) indirectement utilisé par U seulement lorsque mvInnerA est instancié, vous n'avez pas le problème mentionné ci-dessus. Le prix à payer pour cela est la présence d'un <> ennuyeux et le fait que l'on peut passer un type personnalisé à mvInnerA.

+0

Merci pour votre réponse. Malheureusement, je suis au courant de la raison pour laquelle je reçois une erreur (veuillez accepter mes excuses, je vais apporter des modifications à la définition de la question). Cependant, je suis à la recherche d'une solution de contournement. – user1391279

+0

Oh, ok, d'après la question, il semble que vous vous attendez à ce qu'il fonctionne comme il est. Je n'ai pas compris que vous demandiez une approche alternative. Pardon. – skypjack

+0

@ user1391279 Avant d'éditer la réponse, cela fonctionnerait-il pour vous une solution comme [this] (https://wandbox.org/permlink/2JcbrV65osH9BuFW)? En d'autres termes, 'mvInnerA' n'est plus un type mais une fonction dont le type de retour est' T :: inner'. Vous pouvez l'obtenir en utilisant 'decltype' quand vous voulez ou créer des instances du type be means de cette fonction. – skypjack

0

La façon dont vous pouvez utiliser un type interne de paramètre de modèle CRTP est sévèrement limitée.

Il ne peut pas être utile dans la portée de la définition de modèle de classe elle-même. Lors de l'instanciation du modèle, le type B doit être entièrement défini, ce qui n'est pas le cas pour skypjack points out. Vous pouvez cependant l'utiliser dans des contextes qui ne sont pas immédiatement instanciés avec le modèle de classe, qui est principalement les fonctions membres de A.

Et pendant que vous ne pouvez pas avoir un alias de type pour B::Inner, vous pouvez avoir un modèle d'alias de type

template<class C> 
using Inner = typename C::Inner 

Quelles fonctions membres de A peuvent utiliser pour éviter la verbosité de typename B::Inner et utiliser à la place Inner<B>.

+0

Merci pour votre dernier commentaire et pour votre réponse. Malheureusement, comme je l'ai déjà mentionné dans les commentaires et dans la définition de la question, je suis conscient qu'il n'est pas possible de définir la variable membre 'B :: Inner inner' dans' A' et pourquoi cela ne peut pas être fait. Cependant, je voulais comprendre s'il existe des solutions de contournement qui me permettraient d'utiliser 'B :: Inner' dans' A' que je ne me suis pas encore considéré. Malheureusement, je dois admettre que je n'ai pas réussi à fournir une déclaration adéquate du problème. – user1391279