0
votes

I have Iterator template for class and class for use in the for statement.

template<class T>
class Itr2 {
public:
     Itr2()  { }
    ~Itr2()  { }

    typedef typename Itr2 type;
    typedef typename T& reference;

    virtual type& operator++()                      { return *this; }
    virtual T&    operator*()                       { return ((reference)*((type*)this)); }
    virtual bool  operator==(const type& o) const { return true; }
    virtual bool  operator!=(const type& o) const { return false; }
};


template<class T>
class I2
{
public:
    typedef I2<T> type;
    typedef T     value;
    typedef T& reference;
    typedef typename Itr2<T> iterator;

    virtual iterator& begin() { return *(new iterator()); }
    virtual iterator& end()   { return *(new iterator()); }
};

Next, I created class for standard std::vector<>.

template<class T>
class ItrSTD : public Itr2<T> {
public:
    typedef typename Itr2<T> base_type;
    typedef typename ItrSTD<T> type;
    typedef typename T& reference;
    typedef typename std::vector<T>::iterator std_itr;
protected:
    std_itr itr_;
public:
    ItrSTD(const type& o)                               { itr_ = o.itr_; }
    ItrSTD(const std_itr& o)                            { itr_ = o; }

    virtual base_type&  operator++()                    { itr_++; return *this; }

    virtual T&          operator*()                     { return ((reference)(*this->itr_)); }
    bool  operator==(const base_type& o) const override { return (((const type&)o).itr_ == this->itr_); }
    bool  operator!=(const base_type& o) const override { return (((const type&)o).itr_ != this->itr_); }
};



template<class T>
class VSTD : public I2<T> {
protected:
    std::vector<T>  arr_;
public:
    typedef typename ItrSTD<T>  iterator;

    VSTD(const VSTD& o)  { arr_ = o.arr_; }
    template<typename ...E> VSTD(E&&...e) : arr_({ std::forward<T>(e)... }) {  }

    iterator& begin()  _NOEXCEPT  override{ return (*new iterator(arr_.begin())); }
    iterator& end()    _NOEXCEPT  override{ return (*new iterator(arr_.end())); }

};

If i use direct statement for(int i:v). It is works fine, but when I try to do this from the pointer compiler use base class operator!= (not override operator!=) and code doesn't work :(.

int v_i = 0;
VSTD<int> vstd_a = { 1, 2, 3 };
I2<int> *i2 = &vstd_a;

for (int j : *i2) //DOESN't work :( use operator!= from base class
{
v_i += j;
}
for (int j : vstd_a) //work fine :)  use operator!= from VSTD.
{
 v_i += j;
}

If i simplify the code:

template<typename T>
class I3
{
public:
    T i;
    virtual bool  operator==(const I3& o) const { return false; }
};

template<typename T>
class I3O : public I3<T>
{
public:
    virtual bool  operator==(const I3& o) const override { return true; }
};

I3O<int> i3_a, i3_b; I3<int> *i3_ap, *i3_bp;

i3_ap = &i3_a; i3_bp = &i3_b; bool i3_c;

i3_c = (i3_a == i3_b);
i3_c = ((*i3_ap) == (*i3_bp));

Both result are fine (return true) and compare from override class (only for( : ) work bad :( :

Why this happen. It is possible to use for statement for template pointer ? begin(), end() functions works fine. Only operator work different.

P.S. I use VS2013 compiler.

2
You're not really returning *new ... in a function that returns a reference, are you? That's an almost guaranteed memory leak.James Kanze
class Itr2's destructor should probably be virtual, possibly even pure virtual.John Dibling

2 Answers

4
votes

I've not looked at the details in your code, but... It looks like you're trying to make the iterators polymorphic. The standard constantly passes them by value, however, and pass by value doesn't support polymorphism; it slices. Thus (I assume), in:

for ( int j : *i2 )

the compiler creates local variables for the iterator, whose type is determined statically. And although it calls the correct begin(), it assigns the results to this local variable, thus slicing it.

If you need polymorphic iterators, you need to implement the letter/envelope idiom:

class Iterator
{
    Iterator* myImpl;

    virtual Iterator* clone() const { abort(); }
    virtual T& current() const { abort(); }
    virtual bool isEqual( Iterator const* other ) { abort(); }
    virtual void next() { abort(); }

protected:
    Iterator() : myImpl( nullptr ) {}
public:
    Iterator( Iterator* impl ) : myImpl( impl ) {}
    Iterator( Iterator const& other ) : myImpl( other.clone() ) {}
    virtual ~Iterator() { delete myImpl; }

    T& operator*() const
    {
        return myImpl->current();
    }
    bool operator==( Iterator const& other ) const
    {
        return myImpl->isEqual( other.myImpl );
    }
    Iterator& operator++()
    {
        myImpl->next();
        return *this;
    }
    //  ...
};

class DerivedIterator : public Iterator
{
    Iterator* clone() const override { return new DerivedIterator( *this );  }
    T& current() const override { ... }
    bool isEqual( Iterator const* other ) override { ... }
    virtual void next() { ... }
public:
    DerivedIterator(...) ...
};

Then, your derived begin and end return something like:

Iterator( new DerivedIterator(...) );

This is fairly expensive in run-time, but it's really the only way to provide polymorphism and still have the value semantics required by iterators.

1
votes

The standard defines the semantics of the range-based for statement in [stmt.ranged]/1:

For a range-based for statement of the form

for ( for-range-declaration : expression ) statement

let range-init be equivalent to the expression surrounded by parentheses

( expression )

and for a range-based for statement of the form

for ( for-range-declaration : braced-init-list ) statement

let range-init be equivalent to the braced-init-list. In each case, a range-based for statement is equivalent to

{
  auto && __range = range-init;
  for ( auto __begin = begin-expr,
             __end = end-expr;
        __begin != __end;
        ++__begin ) {
    for-range-declaration = *__begin;
    statement
  }
}

where __range, __begin, and __end are variables defined for exposition only, and _RangeT is the type of the expression, and begin-expr and end-expr are determined as follows:

(1.1) — if _RangeT is an array type, ...

(1.2) — if _RangeT is a class type, the unqualified-ids begin and end are looked up in the scope of class _RangeT as if by class member access lookup (3.4.5), and if either (or both) finds at least one declaration, begin-expr and end-expr are __range.begin() and __range.end(), respectively;

(1.3) — otherwise, ...

The first loop in your program:

for (int j : *i2) //DOESN't work :( use operator!= from base class
{
v_i += j;
}

is thus equivalent to:

{
  auto && __range = (*i2);
  for ( auto __begin = __range.begin(),
             __end = __range.end();
        __begin != __end;
        ++__begin ) {
    int j = *__begin;
    v_i += j;
  }
}

Since the return type of I2<int>::begin and I2<int>::end is Itr2<int>&, __begin and __end will be deduced as type Itr2<int>, and copy constructed from the return values of I2<int>::begin() and I2<int>::end(). That copy construction of course slices the returned types, and you end up with instances of the base type.