Andrew McKinlay
Andrew McKinlay

Reputation: 431

Possible to define a C++ variable to accept any BidirectionalIterator?

Whether by good design or not, my main() method declares a variable that needs to expect any iterator that meets the BidirectionalIterator definition (particularly list<>::iterator or vector::iterator) based on what datastructure is being used (determined by input arguments). Since I cannot template the main() method, how does one accomplish this?

For example, instead of:

int main(int argc, const char* argv[]) {
    vector<Person>::iterator iterator0;
    list<Person>::iterator iterator1);
    multimap<string, Person>::iterator iterator2;
}

Since all there iterators meet the requirement of BidirectionalIterator, I want to do:

int main(int argc, const char* argv[]) {
    bidirectionaliterator iterator0;
}

Then, I don't have to check continually what kind of datastructure the program is using with if-statements to store an iterator and use the iterator I want.

Upvotes: 2

Views: 149

Answers (2)

enobayram
enobayram

Reputation: 4708

I think what you're looking for is Type Erasure. You can think of it as the inverse of an interface. You can imagine an interface as sitting at the bottom of a class, determining what methods it needs to provide. A type erased object sort of attaches at the top, and "extracts" certain methods, bringing in duck-typing. There's an upcoming Boost Type Erasure library which will make this an easy to use concept.

Here's how it works in plain C++11 (It can easily be made to work in pre-11 C++ as well, but I wanted to use unique_ptr):

#include<iostream>
#include<vector>
#include<list>
#include<memory>
using namespace std;

class Person{};


template <class T>
class TypeErasedBidirectionalIterator {
public:
    virtual void operator++()=0;
    virtual void operator--()=0;
    virtual T& operator*()=0;
};

template <class T, class Iterator> 
class BidirectionalIteratorAdaptor: public TypeErasedBidirectionalIterator<T> {
    Iterator it;
public:
    BidirectionalIteratorAdaptor(Iterator it): it(it){}
    void operator++(){it++;}
    void operator--(){it--;}
    T& operator*(){*it;}
};

template <class Iterator>
unique_ptr<BidirectionalIteratorAdaptor<typename Iterator::value_type,Iterator> > makeIterator(Iterator it) {
    typedef typename Iterator::value_type T;
    return unique_ptr<BidirectionalIteratorAdaptor<T,Iterator> >(new BidirectionalIteratorAdaptor<T,Iterator>(it));
}

typedef TypeErasedBidirectionalIterator<Person> PersonIterator;
typedef unique_ptr<PersonIterator> PersonIteratorPtr;


int main() {
    vector<Person> vec;
    list<Person> lst;
    lst.push_back(Person());
    PersonIteratorPtr it = makeIterator(vec.begin());
    it = makeIterator(lst.begin());
    ++*it;
    --*it;
    **it;
}

Note that by wrapping the PersonIteratorPtr inside another class that exposes the methods directly you can get rid of the pointer-like behavior, but I didn't want to complicate the proof-of-concept even more.

Upvotes: 1

Jesse Good
Jesse Good

Reputation: 52365

It sounds like you want typedefs:

typedef std::vector<Person> person_container;
typedef person_container::iterator mybidirectionaliterator;

Then anytime you want to change the underlying container, all you have to do is change std::vector<Person> to something else. Although, you still cannot assign any iterator here, you have to use iterators that are compatible. However, are you familiar with the auto keyword in C++11? A lot of times you never have to write out the iterator anymore, i.e. auto myiter = some_container.begin(); would be sufficient.

Also, to get a better answer, it helps to show how you are using the iterator rather than just declaring it.

Upvotes: 1

Related Questions