3

I'm working on a set of n-dimension Cartesian product classes, loosely based off of this solution.

I have many different data types for the same basic set of algorithms and I thought "Aha! I will use templates to lessen my overall work!" And, up until now, it's been working awesome. I'm using Boost's iterator_facade.

My problem is with a derived class I made to work with map<char, boost::integer_range<int> >. Each iteration yields a map<char,int>, but I exclude pairs with a second value of 0 as they're just wasted space as far as my algorithms are concerned.

The derived class overloads the function that generates the iterator's return value and it works. However, during the first iteration, the base class's generator is called. I'm much confused. Does anyone have any ideas?

Here are the relevant code snippets:


#include <boost/container/flat_map.hpp>
#include <boost/tuple/tuple.hpp>
#include <boost/iterator/iterator_facade.hpp>
#include <boost/range/irange.hpp>
#include <utility>
#include <iostream>

using namespace boost;
using namespace boost::tuples;
using namespace std;

template <class Container2DMap,
    class return_type = boost::container::flat_map<typename Container2DMap::value_type::first_type,
    typename Container2DMap::value_type::second_type::value_type> >
class CartProductIterator2DMap : public boost::iterator_facade<
CartProductIterator2DMap<Container2DMap, return_type>,
const return_type,
boost::forward_traversal_tag> {
public:
    typedef typename Container2DMap::value_type::first_type first_type;
    typedef typename Container2DMap::const_iterator first_iterator;
    typedef typename Container2DMap::value_type::second_type::value_type second_type;
    typedef typename Container2DMap::value_type::second_type::const_iterator second_iterator;

    CartProductIterator2DMap(const Container2DMap &container) {
        rangeIterSetup(container);
    }

    CartProductIterator2DMap() : _finished(true) {}
    virtual ~CartProductIterator2DMap() {}
private:
    virtual bool equal(const CartProductIterator2DMap &other) const {
        if (_finished || other._finished) {
            if (_finished && other._finished) {
                return true;
            } else {
                return false;
            }
        } else if (_currentIter == other._currentIter) {
            return true;
        } else {
            return false;
        }
    }
    virtual void increment() { advance(); }
    virtual void advance() {
        advanceIter();
    }

    virtual const return_type& dereference() const { return _currentIter; }

protected:
    struct mode {
        const static bool stopIter = false;
        const static bool continueIter = true;
    };
    typedef boost::tuple<second_iterator,
            second_iterator,
            second_iterator> SecondIterDescription;
    typedef boost::container::flat_map<first_type, SecondIterDescription> RangeIterMap;
    friend class boost::iterator_core_access;
    return_type _currentIter;
    RangeIterMap _rangeIter;
    bool _finished;
    bool _iterMode;

    virtual void advanceIter() {
        if (_iterMode == mode::continueIter) {
            _currentIter = genReturnValue(_rangeIter);
            _iterMode = advanceRangeIter(_rangeIter);
        } else {
            _finished = true;
        }
    }
    virtual void rangeIterSetup(const Container2DMap &container) {
        _finished = false;
        if (container.empty()) {
            _iterMode = mode::stopIter;
        } else {
            _iterMode = mode::continueIter;
            for (typename Container2DMap::const_iterator it =  container.begin();
                    it != container.end(); ++it) {
                _rangeIter.insert(
                        make_pair(it->first,
                                SecondIterDescription(it->second.begin(), it->second.end(), it->second.begin())
                                )
                                );
            }
            advance();
        }
    }


    virtual return_type genReturnValue(const RangeIterMap &rangeIter) {
        std::cout << "Calling base class." << std::endl;
        return_type returnValue;
            for( typename RangeIterMap::const_iterator it = rangeIter.begin();
                    it != rangeIter.end(); ++it) {
                returnValue.insert(
                        make_pair(it->first, *get<2>(it->second))
                        );
            }
        return returnValue;
    }

    virtual bool advanceRangeIter(RangeIterMap &rangeIter) {
        for (typename RangeIterMap::iterator it = rangeIter.begin(); ; ) {
            ++(get<2>(it->second));
            if (get<2>(it->second) == get<1>(it->second)) {
                if (it + 1 == rangeIter.end()) {
                    return mode::stopIter;
                } else {
                    // cascade
                    get<2>(it->second) = get<0>(it->second);
                    ++it;
                }

            } else {
                // normal break point
                return mode::continueIter;
            }
        }
        return mode::continueIter;
    }
};

typedef boost::integer_range<int> _intRange;
typedef boost::container::flat_map<char, _intRange> CharRange;
typedef boost::container::flat_map<char, int> ResidueCount;

template <class Container2D,
    class return_type = boost::container::flat_map<typename Container2D::value_type::first_type,
    typename Container2D::value_type::second_type::value_type> >
struct BaseIterContainer {
    typedef CartProductIterator2DMap<Container2D, return_type> const_iterator;
    const Container2D &_container;
    BaseIterContainer( const Container2D &container) : _container(container) {}
    const_iterator begin() const { return const_iterator(_container); }
    const_iterator end() const { return const_iterator(); }
};

typedef BaseIterContainer<CharRange, ResidueCount> BaseCharRangeIter;

typedef CartProductIterator2DMap<CharRange, ResidueCount> BaseCPIterator;
class DerivedCPIterator : public BaseCPIterator {
public:
    DerivedCPIterator() : BaseCPIterator() {}
    DerivedCPIterator(const CharRange & charRange) : BaseCPIterator(charRange) {}
protected:
    ResidueCount genReturnValue(const RangeIterMap &rangeIter) {
        std::cout << "Calling derived class." << std::endl;
        ResidueCount returnValue;
            for( RangeIterMap::const_iterator it = rangeIter.begin();
                    it != rangeIter.end(); ++it) {
                    const char aa = it->first;
                    const int aaCount = *get<2>(it->second);
                    if (aaCount > 0) {
                        returnValue.insert(
                                make_pair(aa, aaCount)
                        );
                    }
            }
        return returnValue;
    }
};

struct DerivedCharRangeIter {
    typedef DerivedCPIterator const_iterator;
    const CharRange &_container;
    DerivedCharRangeIter( const CharRange &container) : _container(container) {}
    const_iterator begin() const { return const_iterator(_container); }
    const_iterator end() const { return const_iterator(); }
};

std::ostream& operator<<(std::ostream& out, const ResidueCount &rCount) {
    foreach(const ResidueCount::value_type& aaCount, rCount) {
        char aa = aaCount.first;
        int totalAACount = aaCount.second;
        out << "(" << aa << "," << totalAACount << ")";
    }
    return out;
}



int main(int argc, char **argv) {
    cout << "Base Container" << endl;
    CharRange test;
    test.insert(make_pair('a', _intRange(0, 3)));
    test.insert(make_pair('b', _intRange(0, 3)));
    BaseCharRangeIter t(test);
    BaseCharRangeIter::const_iterator it = t.begin();
    for( ;it != t.end(); ++it) {
        cout << *it << endl;
    }
    cout << endl;
    cout << "Derived Container: " << endl;
    DerivedCharRangeIter r(test);
    DerivedCharRangeIter::const_iterator rt = r.begin();
    for( ; rt != r.end(); ++rt) {
        cout << *rt << endl;
    }
    return 0;
}

And the results I get:


Base Container
Calling base class.
(a,0)(b,0)
Calling base class.
(a,1)(b,0)
Calling base class.
(a,2)(b,0)
Calling base class.
(a,0)(b,1)
Calling base class.
(a,1)(b,1)
Calling base class.
(a,2)(b,1)
Calling base class.
(a,0)(b,2)
Calling base class.
(a,1)(b,2)
Calling base class.
(a,2)(b,2)

Derived Container: 
Calling base class.
(a,0)(b,0)
Calling derived class.
(a,1)
Calling derived class.
(a,2)
Calling derived class.
(b,1)
Calling derived class.
(a,1)(b,1)
Calling derived class.
(a,2)(b,1)
Calling derived class.
(b,2)
Calling derived class.
(a,1)(b,2)
Calling derived class.
(a,2)(b,2)

Each genReturnValue print its class (either base or derived) every call. The base class functions as it should. However, the derived class does not. The first iteration calls the base class genReturnValue and the 0s aren't filtered out. However, further iterations do.

Since this is my first foray into templates and derived classes, I'm sure I'm missing something obvious, but for the life of me I can't figure it out. GCC 4.6.3 and Clang 3.0-6 provide the same output.

Halp!

Edit: Also, I'm relatively new at C++ programming. I'm open to critiques, style or otherwise,if you have any. Thanks!

Community
  • 1
  • 1
notwithoutend
  • 235
  • 1
  • 9

2 Answers2

5

It has, actually, nothing to do with pointers; but is, instead, a limitation of the virtual functions.

During construction and destruction virtual calls cannot be purely virtual because of the order of construction and destruction of base classes relative to their derived counterparts, so instead they are statically resolved.

That is, a call to virtualfunc in a constructor or destructor of Base is (somehow) resolved to Base::virtualfunc. If this is a pure virtual function, then Undefined Behavior ensues.

As such, the general guideline is:

  • Never call virtual functions from constructors or destructors.

Example:

struct Base {
    Base() { call(); }

    virtual call() { std::cout << "Base\n"; }
};

struct Derived: Base {
    Derived(int i): _i(i);

    virtual call() { std::cout << "Derived" << _i << "\n"; }

    int _i;
};

int main() {
    Derived d(1);
};

This will print Base, not Derived1 as you might expect; and there is a good reason for this: remember the construction order of things:

  • Derived() is called
  • It automatically calls Base()
  • It calls _i(i)
  • It enters the body of Derived(), which does nothing

Therefore, when Base() is called, _i has not been initialized yet, so a call to call would be foolish. Thankfully the Standard has seen fit to provide a good work-around here, even though it is, most of the time, not what you would first expect.

Matthieu M.
  • 287,565
  • 48
  • 449
  • 722
  • Wow, excellent. Thank you so very much. The reasons make perfect sense, now that I know the underlying strategy. Thank you for taking the time to answer. – notwithoutend May 22 '12 at 18:56
3

You're base class CartProductIterator2DMap has a constructor that calls a virtual function:

CartProductIterator2DMap(const Container2DMap &container) {
    rangeIterSetup(container);
}

this virtual function eventually calls another virtual function: genReturnValue.

Now, when the base class constructor is executing, the derived class has not been constructed yet. So the call to genReturnValue from the base class constructor invokes CartProductIterator2DMap::genReturnValue. But subsequent calls (after the object is fully constructed) invokes: DerivedCPIterator::genReturnValue.

You need to make sure that your base class constructor does not invoke any virtual functions.

zdan
  • 28,667
  • 7
  • 60
  • 71