4

Je suis étudiant en cours de POO (C++ est une langue de base) à l'université. Ma tâche consiste à implémenter sa propre classe de conteneur de modèles de liste chaînée. Je l'ai fait presque complètement mais j'ai été confronté à un problème. Il est connu que STL fournit iterator et const_iterator classes pour l'itération par liste. Ils ont presque la même implémentation, la différence majeure est que les méthodes d'itérateur renvoient des références alors que les méthodes de const_iterator - les références constantes. J'ai suivi https://stackoverflow.com/a/3582733/2108548 et créé la classe de modèles séparés ListIterator. Puis j'ai déclaré avec typedef classes Iterator et ConstIterator à l'intérieur de la classe List.Convertir l'objet de la classe d'itération basée sur un modèle personnalisé en const_iterator

J'ai quelque chose comme ceci:

template<typename T> 
class ListNode 
{ 
public: 
    ListNode(T *node_value = nullptr, ListNode *node_prev = nullptr, ListNode *node_next = nullptr): 
     value(node_value), prev(node_prev), next(node_next) { } 

    T *value; 
    ListNode *prev, *next; 
}; 

template<typename T> 
class ListIterator 
{ 
    typedef ListNode<T> Node; 

public: 
    ListIterator(); 
    ListIterator(Node *node); 
    ListIterator(ListIterator const &other); 
    ListIterator &operator++(); 
// ... 
    Node *i; 
}; 

template<typename T> 
class List: public Container 
{ 
    typedef ListIterator<T> Iterator; 
    typedef ListIterator<T const> ConstIterator; 
// ... 
    Iterator begin() const 
    { 
     return Iterator(m_first->next); 
    } 
    ConstIterator const_begin() const 
    { 
     return ConstIterator(begin()); 
    } 
// ... 
}; 

Tout fonctionnait très bien jusqu'à ce que je décide de faire "copier-constructeur" Iterator ->ConstIterator. J'ai donc besoin de la méthode constructeur qui obtient ListIterator<T> (où T est le nom de la classe de données) et crée un nouveau type d'objet ListIterator<T const>. Mais en fait, le constructeur de ConstIterator obtient T const en tant que paramètre de modèle, donc j'ai besoin de supprimer const pour le paramètre du constructeur. J'ai trouvé l'en-tête type_traits qui fait cela. J'ai donc écrit "copy-constructor": typedef typename std :: remove_cv :: type NoConstT; ListIterator (ListIterator const & autre);

Mais ça ne marche pas! J'ai eu cette erreur après avoir demandé const_begin():

List<int> list1; 
list1 << 1 << 2 << 3; 
int i = *list1.const_begin(); 

error: 'ListIterator<T>::ListIterator(const ListIterator<typename std::remove_cv<_Tp>::type>&) [with T = int; typename std::remove_cv<_Tp>::type = int]' cannot be overloaded with 'ListIterator<T>::ListIterator(const ListIterator<T>&) [with T = int; ListIterator<T> = ListIterator<int>]' 

Mais ce n'est pas tout. Pour atteindre mon objectif, vous devez également convertir ListNode<T> en ListNode<T const>. Mais j'ai encore un problème là: chaque nœud de liste contient des pointeurs vers les nœuds précédents et suivants et si j'essaie de les initialiser dans le constructeur du nœud, j'obtiendrai la récursivité. Bien sûr, je peux créer une fonction qui gère la conversion de tous les nœuds ListNode<T> en ListNode<T const> par itération à travers eux. Mais je n'aime pas cette solution: elle a d'énormes frais généraux! J'ai demandé cette question à mon professeur. Il ne pouvait pas le comprendre pendant plusieurs minutes, puis quand il l'a eu, il a dit: "C'est élémentaire!" - "Mais je suis resté avec ça pendant 3-4 heures!" - "Si oui, jetez les constitateurs et finalisez le conteneur de la liste sans eux, j'ai besoin de temps pour comprendre votre code" (comme vous voyez, mon code est assez simple à mon avis). Comme je l'ai compris, il ne connaissait pas la réponse à cette question. Mais je veux vraiment savoir comment le faire! Comment puis-je résoudre ce problème?

Désolé pour des tonnes d'erreurs - Je ne suis pas un locuteur natif anglais.

+1

Pouvez-vous également ajouter le principal qui a créé ces erreurs? – Alon

+0

Avez-vous regardé comment cela est géré pour les types STL? Par exemple, mon implémentation 'std :: vector :: iterator' est en fait une spécialisation de' std :: vector :: const_iterator'. – Chad

+0

@Alon mis à jour la description de l'erreur – rominf

Répondre

3

Vous pouvez en effet utiliser <type_traits>, mais pas de la manière que vous décrivez. Une approche consisterait à toujours déclarer un constructeur du même type et à déclarer à partir de non-const une conditionnelle avec enable_if uniquement lorsque l'argument template n'est en effet pas constant. Et nœud devrait toujours être non-const, cela vous pouvez le faire avec remove_const.

#include <type_traits> 

template<typename T> 
class ListNode 
{ 
    //... 
}; 

template<typename T> 
class ListIterator 
{ 
    typedef ListNode<typename std::remove_const<T>::type> Node; 

public: 
    ListIterator() {} 
    ListIterator(Node*) {} 
    ListIterator(ListIterator const&) {} 
    template <typename U> 
    ListIterator(ListIterator<U> const&, typename std::enable_if<!std::is_const<U>()>::type* = nullptr) {} 
}; 

template<typename T> 
class List 
{ 
public: 
    typedef ListIterator<T> Iterator; 
    typedef ListIterator<T const> ConstIterator; 
// ... 
    Iterator begin() 
    { 
     return Iterator(/*...*/); 
    } 
    ConstIterator const_begin() const 
    { 
     return ConstIterator(/*...*/); 
    } 
// ... 
}; 

int main() { 
    List<int> list; 
    List<int>::ConstIterator ci = list.const_begin(); 
    List<int>::Iterator i = list.begin(); 
    ci = i; // works fine as expected 
    i = ci; // fails as expected 
    i = i; // works fine as expected 
    ci = ci; // works fine as expected 
    return 0; 
} 
+0

Merci beaucoup! Cela fonctionne parfaitement! Avec cette solution, il n'y a pas besoin de la méthode const_begin. C'est bon aussi. – rominf

+0

C'est merveilleux, je savais qu'il devait y avoir une meilleure façon d'exprimer des types d'itérateurs mutables et immuables que de propager des plaques de chaudières de méta-modèle dans tout le continuum classe/temps. –

Questions connexes