4

What it the right way to write a wrapper class around an STL container, which is also a template (can accept a generic type T as element) and allows me to use an iterator as I would do with the STL container directly?

I want to do something of the following type

#include <list>
#include <iostream>

class MyClass{};

template<class T>
class Wrapper
{
    public:
        typename std::list<T>::iterator iterator;

        std::list<T> elements;
        iterator begin(){ return elements.begin(); };
        iterator end(){ return elements.end(); };
};

int main()
{
    Wrapper<MyClass> wrapper;

    for (Wrapper::iterator it = wrapper.begin(); it != wrapper.end(); ++it)
        std::cout<<"Hi"<<std::endl;
}

But the compiler says:

 error: ‘iterator’ in ‘class Wrapper<T>’ does not name a type
Andrea Araldo
  • 1,332
  • 14
  • 20
  • 6
    `typedef typename std::list::iterator iterator;` or `using iterator = typename std::list::iterator;` . As written, `iterator` is a data member, not a member type. – Igor Tandetnik Aug 11 '17 at 21:44

2 Answers2

2

You have two errors.

Like Igor Tandetnik said in a comment, your iterator is a data member, not a nested type of your case.

You have to do this in your class:

typedef typename std::list<T>::iterator iterator;

or, in C++11:

using iterator = typename std::list<T>::iterator;

Also, you are using iterator the wrong way in your main() code. It should be like this:

Wrapper<MyClass>::iterator it = wrapper.begin()

Or, in C++ 11 or later, you can do this:

for(const auto &element : wrapper) {
    ...
}

Personally, I would prefer to use private inheritance instead of encapsulation. In my head, public inheritance means an "IS A" relationship, whereas private inheritance means an "IS IMPLEMENTED IN TERM OF" relationship.

You could do it like this:

template<typename T>
class WrapperList : private List<T> {
    ... Your code that belongs to your wrapper
}
Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
Antoine Morrier
  • 3,930
  • 16
  • 37
1

If you want to wrap std::list and enrich it by some functionalities and, essentially, maintain some (or most) of the std::list interface (as the iteration capabilities) also in the wrapper, there are (at least) two possibilities.

One (composition) is to define begin(), end() methods and iterator type in the wrapper as to propagate them to the contained structure, as proposed by Andrea Araldo.

One (private inheritance) is to leverage private inheritance, which can be quite handy in some cases. Steps needed:

  • privately derive from std::list
  • implement the wrapper-specific methods, if any (what you need to add in the wrapper)
  • expose in the wrapper interface the methods and types of the std::list public interface, which you are interested in, through the 'using-declaration' (http://en.cppreference.com/w/cpp/language/using_declaration).

For example, in the following there is a slight modification of the proposed snippet in this sense (WrapperPrivInh). In this case, also all the standard constructors of std::list are made available in the wrapper interface, for free. Furthermore, for completeness, also WrapperCompos variation is proposed, where the list is contained into the wrapper, which exposes what is needed to support iterator-based loop. In this case, only the 'number of default elements' constructor is re-defined from scratch.

#include <list>
#include <iostream>

class MyClass
{ 
public:
    MyClass() { std::cout<<"(DEBUG)MyClass::default constructor\n"; }
};

template<class T>
class WrapperPrivInh : private std::list<T>
{
    public:
        using std::list<T>::iterator;  //for the iterator type
        using std::list<T>::begin;     // for the begin()
        using std::list<T>::end;       //  and end() methods
        using std::list<T>::list;      // for the constructors, if needed

        //std::list<T> elements;
        //iterator begin(){ return elements.begin(); };
        //iterator end(){ return elements.end(); };
};

template<class T>
class WrapperCompos 
{
    std::list<T> m_list;

    public:
        using iterator= typename std::list<T>::iterator;  //for the iterator type

        WrapperCompos(int const n) : m_list(n) { }

        iterator begin(){ return m_list.begin(); };
        iterator end(){ return m_list.end(); };
};


int main()
{
    {
        std::cout<<"Experiment with private inheritance"<<'\n';
        WrapperPrivInh<MyClass> wrapper(3);  // constructor witch builds N (3 here) MyClass default elements (derived "for free" from std::list)

        for(WrapperPrivInh<MyClass>::iterator it = wrapper.begin(); it != wrapper.end(); ++it)
            std::cout<<"Hi "<<&(*it)<<std::endl;
    }

    {
        std::cout<<"\nExperiment with private inheritance"<<'\n';
        WrapperCompos<MyClass> wrapper(3);  // constructor witch builds N (3 here) MyClass default elements (derived "for free" from std::list)

        for(WrapperCompos<MyClass>::iterator it = wrapper.begin(); it != wrapper.end(); ++it)
            std::cout<<"Hi "<<&(*it)<<std::endl;
        for( auto const& x : wrapper )  //range-for-loop syntax (from c++11)
            std::cout<<"Hi2 "<<&x<<std::endl;
    }
}

The private inheritance approach can be interesting to easily, and correctly, propagate the features of the wrapped structure to the wrapper. For instance the wrapper can be easily made compatible with std::algorithms if the wrapped structure is compatible with them, like std::list is. Hope this helps. Best

Sandro
  • 37
  • 4
  • Deriving from standard containers - whether privately or not - is also considered bad practice. There is plenty of information around which explains why. – Peter Aug 12 '17 at 00:33
  • Which are the main drawbacks of private inheritance in this specific case where the main objective appears to be to wrap a std::list for maybe adding some functionality and reusing at least part of the std::list public interface ? – Sandro Aug 12 '17 at 00:58
  • Read the second half of the answer by ex0du5 (it has a 50 point bounty) at https://stackoverflow.com/questions/6806173/subclass-inherit-standard-containers. – Peter Aug 12 '17 at 01:18
  • Such post appears to focus more specifically on public inheritance and I strongly agree with it. It never mentions private inheritance and most of the suggestions fit far better public inheritance. For sure private inheritance has its own flaws but the major critical points highlighted in such answer do not affect private inheritance significantly. For most of the issues raised there, private inheritance behaves like composition ("is implemented in terms of" to put it in Scott Meyers words), which is suggested in such post. – Sandro Aug 12 '17 at 15:26
  • 1
    The concerns in the second half of that answer are applicable to inheritance, regardless of whether (in C++) that is public, private, or protected. You're thinking in terms of ease of reuse of the standard container in your class, whereas I'm thinking in terms of ability of someone else to reuse functionality from your class. Inheritance from a class that is not intended for use as a base class inhibits reuse of the functionality from your class due to breaking away from some principles that support reuse, such as open-closed, liskov substitution, etc. – Peter Aug 12 '17 at 21:48
  • Frankly I don't agree on the conclusion that the concerns of such post apply directly to also private inheritance, which is de-facto very similar to composition. In fact the derived class can use only the public methods of the privately inherited class (as in composition) and does not offer them in its public interface (the derived class is not the base class...so no slicing, etc.). However thanks for pointing out these info as I learned from them. I edited the answer including both solutions: composition and private inheritance. – Sandro Aug 16 '17 at 10:17
  • Frankly, I think you're missing the point completely, because you are focusing on your ability to construct your class, not on the ability of someone else to reuse functionality your class introduces. I'll leave it there. – Peter Aug 16 '17 at 10:37