#ifndef SRK31_SELECTIVE_ITERATOR_HPP_ #define SRK31_SELECTIVE_ITERATOR_HPP_ #include #include #include /* Most of the time, you should use boost::filter_iterator instead. * But this class applies the predicate to the underlying iterator, * not to the object yielded by dereferencing it. So this variant is * useful if there is some nontrivial cost to dereferencing the iterator, * and the predicate can be evaluated on the iterator without dereferencing. * * Another benefit we have is that we provide the core of the iterator as * a CRTP mixin. The idea here is to avoid boost:: iterators' need for base() * calls if you want to access methods on the underlying iterator. * * To get iterator class J backed by iterators of class I, * rather than creating a completely new iterator class with an encapsulated * J, like this: * * typedef selective_iterator J * * ... we derive a new class * * struct J : I, selective_iterator_mixin {}; * * such that J is both an I and a selective I. * * Then there's no need to call base(). The utility methods on I are available * directly, but the relevant behaviour is overloaded in the mixin. * * PROBLEM: do we want to leave room for impls where the base iterator * instance is actually not "this" but a contained field? Hmm. NO! * * PROBLEM: operator overload ambiguity: how to tell the compiler * that the selective_iterator_mixin's definitions take precedence * over I's? */ namespace srk31 { template struct selective_iterator_mixin : public std::iterator_traits // borrow container's typedefs { bool have_begin; /* const */ Iter m_begin; /* const */ Iter m_end; Pred m_pred; typedef selective_iterator_mixin self; typedef MixerIn super; /* We don't have m_iter. The idea is that "*this", * appropriately static_cast'd, is m_iter. * * This has some surprising consequences. Although we * want to define various operators and constructors * here, so that the mixer-in doesn't have to (it can just * do "using" them), it is confusing in some cases. * Constructors, including copy and move constructors, * become responsible for initialising/copying/moving the * *superobject*'s fields. I *think* this works.... */ const Iter& iter() const { return *static_cast(this); } Iter& iter() { return *static_cast< MixerIn *>(this); } void print_range() { // std::cerr << "selective_iterator_mixin selecting over range: "; // unsigned count = 0; // for (Iter i = this->m_begin; i != this->m_end; i++) // { // //std::cerr << *i; // //std::cerr << " "; // ++count; // } // std::cerr << "of " << count << " elements" << std::endl; } // how to support non-copying? get rid of "begin"? // Then we are "forward_selective_iterator". // We could just tolerate the copy of the first iterator of every subseq. // In that case I've already solved it above: just use the fully-specifying constructor. // BUT we want to support the forward-only case. // No termination check. /* These constructors do not match copy/move signatures, * so we are okay to initialise iter() here (cf. see below for copy/move). * We are effectively writing over the top of a default-constructed * subobject. Be careful with the rvalue refs, though. */ // fully specifying constructor selective_iterator_mixin(Iter&& begin, Iter&& end, Iter&& val, const Pred& pred = Pred()) : m_begin(std::move(begin)), m_end(std::move(end)), m_pred(pred) { //std::cerr << "Three-arg moving constructor called for " << this << std::endl; iter() = std::move(val); assert(static_cast(*this) == this->m_end || this->m_pred(static_cast(*this))); print_range(); } // fully specifying constructor selective_iterator_mixin(const Iter& begin, const Iter& end, const Iter& val, const Pred& pred = Pred()) : m_begin(begin), m_end(end), m_pred(pred) { //std::cerr << "Three-arg non-moving constructor called for " << this << std::endl; iter() = val; assert(static_cast(*this) == this->m_end || this->m_pred(static_cast(*this))); print_range(); } // begin/end only -- COPIES BOTH selective_iterator_mixin(const Iter& begin, const Iter& end, const Pred& pred = Pred()) : have_begin(true), m_begin(begin), m_end(end), m_pred(pred) { //std::cerr << "Two-arg non-moving constructor called for " << this << std::endl; iter() = begin; while (iter() != this->m_end && !this->m_pred(iter())) { //std::cerr << "Advancing (1) over one element because pred says no" << std::endl; ++iter(); } print_range(); } // begin/end only -- MOVES BOTH selective_iterator_mixin(Iter&& begin, Iter&& end, const Pred& pred = Pred()) : have_begin(true), m_begin(std::move(begin)), m_end(std::move(end)), m_pred(pred) { //std::cerr << "Two-arg moving constructor called for " << this << std::endl; iter() = m_begin; while (static_cast(*this) != this->m_end && !this->m_pred(static_cast(*this))) { //std::cerr << "Advancing (2) over one element because pred says no" << std::endl; ++iter(); } print_range(); } /* Copy and move constructors. * CARE. These are not as simple as they seem. * We want them to be inheritable by the mixer-in. * BUT what does constructor inheritance mean, in a * multiple-inheritance scenario? Which constructors * are called for the other bases? * I think that copy and move constructors are not actually * inherited, but that a defaulted version is generated * which copy/move-constructs each inherited subobject * separately. * The upshot is that we should *not* assign to iter() here. * It will get done *before* we are called. * This is particularly an issue with move semantics: the * iterator subobject will already have been moved out of * the argument object, so we should not use that subobject * in the argument (it will be zeroed). * * What about copy/move-assignment operators? * I *think* they are treated the same way. */ // copy constructor selective_iterator_mixin(const self& arg) : have_begin(arg.have_begin), m_begin(arg.m_begin), m_end(arg.m_end), m_pred(arg.m_pred) { // print_range(); } // move constructor selective_iterator_mixin(self&& arg) : have_begin(std::move(arg.have_begin)), m_begin(std::move(arg.m_begin)), m_end(std::move(arg.m_end)), m_pred(arg.m_pred) { // print_range(); } // assignment self& operator=(const self& arg) { //assert(arg.m_begin == this->m_begin); //assert(arg.m_end == this->m_end); this->m_begin = arg.m_begin; this->m_end = arg.m_end; this->m_pred = arg.m_pred; this->have_begin = arg.have_begin; //iter() = arg.iter(); // the mixer-in takes care of this by using default operator= print_range(); return *this; } self& operator=(self&& arg) // move assignment { //assert(arg.m_begin == this->m_begin); //assert(arg.m_end == this->m_end); this->m_begin = std::move(arg.m_begin); this->m_end = std::move(arg.m_end); this->m_pred = std::move(arg.m_pred); this->have_begin = std::move(arg.have_begin); //iter() = std::move(arg.iter()); // the mixer-in takes care of this by using default operator= print_range(); return *this; } typename std::iterator_traits::reference operator*() const { return *iter(); } typename std::iterator_traits::pointer operator->() const { return &*iter(); } self& operator++() // prefix { do { ++iter(); } while (iter() != m_end && !m_pred(iter())); return *this; } self operator++(int) // postfix ++, so copying { Iter tmp = iter(); ++*this; // re-use prefix form // return tmp return self(m_begin, m_end, tmp, m_pred); } self& operator--() // prefix -- { // our "--" is a bit broken if we didn't copy the BEGIN iterator do { --iter(); } while ((!have_begin || (iter() != m_begin)) && !m_pred(iter())); // if we reached the beginning and pred is still false, // this means undefined behaviour is okay (the user shouldn't have // called us) so stop at one memory location *beyond* begin if ((!have_begin || iter() == m_begin) && !m_pred(iter())) --iter(); return *this; } self operator--(int) // postfix, so copying { Iter tmp = iter(); --*this; return self(m_begin, m_end, tmp, m_pred); } bool operator==(const self& arg) const { return iter() == arg.iter(); } bool operator!=(const self& arg) const { return iter() != arg.iter(); } }; template class selective_iterator : public Iter , public selective_iterator_mixin > { //Iter m_iter; typedef selective_iterator self; typedef selective_iterator_mixin super; public: // no default constructor -- we rely on Iter being default-constructable using super::selective_iterator_mixin; using super::operator++; using super::operator--; //usine super::operator=; using super::operator!=; using super::operator==; using super::operator*; using super::operator->; //using Iter::reference; typedef typename Iter::reference reference; typedef typename Iter::difference_type difference_type; typedef typename Iter::value_type value_type; typedef typename Iter::pointer pointer; typedef typename Iter::iterator_category iterator_category; }; } // end namespace srk31 #endif