The detailed analysis of corner cases indicated a lot of hidden dangers when stretching the `IterExplorer` design beyond its limits. By deliberate choice, this design was kept open so that advanced cohesive implementation structures can be built. It is even possible ''and tolerated'' to rely on ''references'' to some degree -- assuming however that the referred memory location remains stable. As it turned out, these basic assumptions can be broken by a combination of read-ahead processing (as is necessary for grouping and filtering), together with adaptor objects embedded as value, especially when the resulting pipeline object is then moved or copied during evaluation. While my general attitude towards such extremely clever implementation constructs is "don't shoot yourself into your foot unless you need to" -- it seems prudent to clean-up some crappy code from the first attempts towards an iterator pipeline -- notably some of the »Itertools«.
500 lines
15 KiB
C++
500 lines
15 KiB
C++
/*
|
||
ITER-SOURCE.hpp - an interface to build an opaque iterator-based data source
|
||
|
||
Copyright (C)
|
||
2010, Hermann Vosseler <Ichthyostega@web.de>
|
||
|
||
**Lumiera** is free software; you can redistribute it and/or modify it
|
||
under the terms of the GNU General Public License as published by the
|
||
Free Software Foundation; either version 2 of the License, or (at your
|
||
option) any later version. See the file COPYING for further details.
|
||
|
||
*/
|
||
|
||
/** @file iter-source.hpp
|
||
** Extension module to build an opaque data source, accessible as
|
||
** **Lumiera Forward Iterator**. It is based on combining an IterAdapter
|
||
** with classical polymorphism; here, the data source, which is addressed
|
||
** by IderAdapter through the "iteration control API", is abstracted behind
|
||
** an interface (with virtual functions). Together this allows to build
|
||
** a simple data source type, without needing to disclose details of
|
||
** the implementation.
|
||
**
|
||
** @todo the design used for the "iteration control API" is misaligned
|
||
** with the purpose of this adapter. Rather, it should be shaped
|
||
** similar to IterStateWrapper with three control functions //////////////////////////////////////TICKET #1125
|
||
**
|
||
** # Standard Adapters
|
||
** As a complement, this header contains a generic implementation
|
||
** of the IterSource interface by wrapping an existing Lumiera Forward Iterator.
|
||
** Using this WrappedLumieraIter, the details of this wrapped source iterator
|
||
** remain opaque. To ease the use of this adapter, a selection of free functions
|
||
** is provided, allowing to build opaque "all elements" or "all keys" iterators
|
||
** for various STL containers.
|
||
**
|
||
** @see iter-adapter.hpp
|
||
** @see itertool.hpp
|
||
** @see iter-source-test.cpp
|
||
**
|
||
*/
|
||
|
||
|
||
#ifndef LIB_ITER_SOURCE_H
|
||
#define LIB_ITER_SOURCE_H
|
||
|
||
|
||
#include "lib/meta/util.hpp"
|
||
#include "lib/meta/function.hpp"
|
||
#include "lib/iter-adapter.hpp"
|
||
#include "lib/itertools.hpp"
|
||
#include "lib/nocopy.hpp"
|
||
|
||
#include <type_traits>
|
||
#include <utility>
|
||
#include <string>
|
||
#include <memory>
|
||
|
||
|
||
|
||
|
||
namespace lib {
|
||
|
||
using std::string;
|
||
using std::shared_ptr;
|
||
using std::forward;
|
||
|
||
|
||
|
||
/**
|
||
* Iteration source interface to abstract a data source,
|
||
* which then can be accessed through IterAdapter as a frontend,
|
||
* allowing to pull individual elements until exhaustion.
|
||
*
|
||
* This base class is empty and makes no assumptions regarding
|
||
* identity, instantiation and copying.
|
||
*
|
||
* @see PlacementIndex::Table#_eachEntry_4check usage example
|
||
* @see iter-source-test.cpp
|
||
*/
|
||
template<typename TY>
|
||
class IterSource
|
||
{
|
||
|
||
protected: /* == data source API to implement == */
|
||
|
||
using Pos = TY*;
|
||
using DataHandle = shared_ptr<IterSource>;
|
||
|
||
///////////////////////////////////////////////TICKET #1125 : this API should use three control functions, similar to IterStateWrapper
|
||
/** iteration start: prepare the first element.
|
||
* may return NULL in case of empty data source
|
||
*/
|
||
virtual Pos firstResult () =0;
|
||
|
||
/** iteration step: switch on to the next element.
|
||
* The pos pointer should be set to NULL to report
|
||
* iteration end
|
||
*/
|
||
virtual void nextResult(Pos& pos) =0;
|
||
|
||
/** disconnect the data source / iteration frontend.
|
||
* Depending on the way the IterSource got created
|
||
* this \em might (or might not) be followed by
|
||
* destroying the data source.
|
||
* @note must not throw
|
||
*/
|
||
virtual void disconnect () { }
|
||
|
||
|
||
|
||
public:
|
||
virtual ~IterSource() { }; ///< is ABC
|
||
|
||
virtual
|
||
operator string() const ///< subclasses may offer diagnostics
|
||
{
|
||
return "IterSource<" + lib::meta::typeStr<TY>()+ ">";
|
||
}
|
||
|
||
|
||
/* == Iteration control API for IterAdapter frontend == */
|
||
|
||
friend bool ////////////////////////////////////////TICKET #1125 : this API should use three control functions, similar to IterStateWrapper
|
||
checkPoint (DataHandle const&, Pos const& pos)
|
||
{
|
||
return bool(pos);
|
||
}
|
||
|
||
friend void
|
||
iterNext (DataHandle& source, Pos& pos)
|
||
{
|
||
ASSERT (source);
|
||
source->nextResult(pos);
|
||
}
|
||
|
||
|
||
/* == public builder API to create instances == */
|
||
|
||
struct iterator
|
||
: IterAdapter<Pos, DataHandle>
|
||
{ ////////////////////////////////////////////////////TICKET #1125 : should be build on top of IterStateWrapper rather than IterAdapter!
|
||
using _I = IterAdapter<Pos, DataHandle>;
|
||
using _I::IterAdapter;
|
||
|
||
operator string() const {return _I::source()? string(*_I::source()) : "⟂"; }
|
||
};
|
||
|
||
/** build an iterator frontend for the given source,
|
||
* @note the source is allocated separately and
|
||
* \em not owned by the iterator frontend
|
||
*/
|
||
static iterator
|
||
build (IterSource& sourceImpl)
|
||
{
|
||
return startIteration (DataHandle{&sourceImpl, &detach_without_destroy});
|
||
}
|
||
|
||
/** build an iterator frontend, thereby managing
|
||
* the given heap allocated source object instance.
|
||
* @note we take ownership and destroy the source
|
||
* when the last copy of the created iterator
|
||
* goes out of scope.
|
||
*/
|
||
static iterator
|
||
build (IterSource* sourceImplObject)
|
||
{
|
||
return startIteration (DataHandle{sourceImplObject, &destroy_managed_source});
|
||
}
|
||
|
||
static iterator EMPTY_SOURCE;
|
||
|
||
using value_type = TY;
|
||
using reference = TY&;
|
||
using pointer = TY*;
|
||
|
||
|
||
|
||
private:
|
||
static iterator
|
||
startIteration (DataHandle sourceHandle)
|
||
{
|
||
REQUIRE (sourceHandle);
|
||
Pos first = sourceHandle->firstResult();
|
||
return {move(sourceHandle), first};
|
||
}
|
||
|
||
|
||
static void
|
||
detach_without_destroy (IterSource * source)
|
||
{
|
||
WARN_IF (!source, library, "IterSource deleter called with NULL source pointer");
|
||
if (source)
|
||
source->disconnect();
|
||
}
|
||
|
||
static void
|
||
destroy_managed_source (IterSource * source)
|
||
{
|
||
WARN_IF (!source, library, "IterSource deleter called with NULL source pointer");
|
||
if (source)
|
||
{
|
||
source->disconnect();
|
||
delete source;
|
||
}
|
||
}
|
||
};
|
||
|
||
|
||
/** storage for the empty data-source constant */
|
||
template<typename TY>
|
||
IterSource<TY>::iterator IterSource<TY>::EMPTY_SOURCE = iterator();
|
||
|
||
|
||
|
||
|
||
|
||
/**
|
||
* Standard implementation of the IterSource interface:
|
||
* a wrapped "Lumiera Forward Iterator". Usually, such a wrapper instance
|
||
* is passed to one of the IterSource's builder functions, thereby
|
||
* erasing the specific type information of the template parameter IT
|
||
*/
|
||
template<class IT, class ISO = IterSource<typename meta::ValueTypeBinding<IT>::value_type>>
|
||
class WrappedLumieraIter
|
||
: public ISO
|
||
, util::NonCopyable
|
||
{
|
||
IT src_;
|
||
|
||
protected:
|
||
using Pos = ISO::Pos;
|
||
|
||
Pos ////////////////////////////////////////////////////TICKET #1125 : this API should use three control functions, similar to IterStateWrapper
|
||
firstResult ()
|
||
{
|
||
if (!src_)
|
||
return 0;
|
||
else
|
||
return & *src_;
|
||
}
|
||
|
||
void
|
||
nextResult (Pos& pos)
|
||
{
|
||
if (!pos) return;
|
||
if (src_) ++src_;
|
||
if (src_)
|
||
pos = & *src_;
|
||
else
|
||
pos = 0;
|
||
}
|
||
|
||
|
||
|
||
public:
|
||
WrappedLumieraIter (IT const& orig)
|
||
: src_(orig)
|
||
{ }
|
||
|
||
WrappedLumieraIter (IT&& orig)
|
||
: src_(forward<IT>(orig))
|
||
{ }
|
||
|
||
|
||
protected:
|
||
IT& wrappedIter() { return src_; }
|
||
IT const& wrappedIter() const { return src_; }
|
||
};
|
||
|
||
|
||
|
||
|
||
/* === pre-defined Adapters for frequently used Containers === */
|
||
|
||
namespace iter_source {
|
||
namespace { // traits and helpers...
|
||
|
||
template<class CON>
|
||
struct _SeqT
|
||
{
|
||
using Val = CON::iterator::value_type;
|
||
using Iter = IterSource<Val>::iterator;
|
||
};
|
||
|
||
template<class IT>
|
||
struct _RangeT
|
||
{
|
||
using Val = IT::value_type;
|
||
using Iter = IterSource<Val>::iterator;
|
||
};
|
||
|
||
template<class MAP>
|
||
struct _MapT
|
||
{
|
||
using Key = MAP::key_type;
|
||
using Val = MAP::value_type::second_type;
|
||
using KeyIter = IterSource<Key>::iterator;
|
||
using ValIter = IterSource<Val>::iterator;
|
||
};
|
||
|
||
|
||
template<class IT>
|
||
struct _IterT
|
||
{
|
||
using Src = std::remove_reference<IT>::type;
|
||
using Val = Src::value_type;
|
||
using Iter = IterSource<Val>::iterator;
|
||
};
|
||
|
||
template<class IT, class FUN>
|
||
struct _TransformIterT
|
||
{
|
||
using Src = std::remove_reference<IT>::type;
|
||
using ResVal = lib::meta::_Fun<FUN>::Ret;
|
||
using TransIter = decltype(lib::transformIter (std::declval<IT>(), std::declval<FUN>()));
|
||
using Iter = IterSource<ResVal>::iterator;
|
||
};
|
||
|
||
template<class IT>
|
||
struct _PairIterT
|
||
{
|
||
using Src = std::remove_reference<IT>::type;
|
||
using PairType = Src::value_type;
|
||
using ValType = PairType::second_type;
|
||
using ConstKeyType = PairType::first_type;
|
||
|
||
// since we're returning the keys always by value,
|
||
// we can strip the const added by the STL map types
|
||
using KeyType = std::remove_const<ConstKeyType>::type;
|
||
|
||
static KeyType takeFirst (PairType const& pair) { return pair.first; }
|
||
static ValType takeSecond(PairType const& pair) { return pair.second;}
|
||
};
|
||
|
||
|
||
template<class IT>
|
||
auto
|
||
takePairFirst (IT&& source)
|
||
{
|
||
return transformIter (forward<IT>(source), _PairIterT<IT>::takeFirst );
|
||
}
|
||
|
||
template<class IT>
|
||
auto
|
||
takePairSecond (IT&& source)
|
||
{
|
||
return transformIter (forward<IT>(source), _PairIterT<IT>::takeSecond );
|
||
}
|
||
|
||
} //(END) type helpers...
|
||
|
||
|
||
|
||
/** wraps a given Lumiera Forward Iterator,
|
||
* exposing just a IterSource based frontend.
|
||
*/
|
||
template<class IT>
|
||
_IterT<IT>::Iter
|
||
wrapIter (IT&& source)
|
||
{
|
||
using Src = _IterT<IT>::Src;
|
||
using Val = _IterT<IT>::Val;
|
||
|
||
return IterSource<Val>::build (new WrappedLumieraIter<Src> (forward<IT>(source)));
|
||
}
|
||
|
||
|
||
/** an IterSource frontend to return just a single value once.
|
||
* @warning behind the scenes, a heap allocation is managed by shared_ptr,
|
||
* to maintain a copy of the wrapped element. When passing a reference,
|
||
* only a reference will be wrapped, but a heap allocation happens nonetheless
|
||
*/
|
||
template<typename VAL>
|
||
auto
|
||
singleVal (VAL&& something)
|
||
{
|
||
using Src = decltype(singletonIter (forward<VAL>(something)));
|
||
using Val = _IterT<Src>::Val;
|
||
|
||
return IterSource<Val>::build (new WrappedLumieraIter<Src>{singletonIter (forward<VAL>(something))});
|
||
}
|
||
|
||
|
||
/** pipes a given Lumiera Forward Iterator through
|
||
* a transformation function and wraps the resulting
|
||
* transforming Iterator, exposing just an IterSource.
|
||
* This convenience shortcut can be used to build a
|
||
* processing chain; the resulting IterSource will
|
||
* hide any detail types involved.
|
||
* @note as with any IterSource, there is one virtual
|
||
* function call for every fetched element.
|
||
*/
|
||
template<class IT, class FUN>
|
||
_TransformIterT<IT,FUN>::Iter
|
||
transform (IT&& source, FUN processingFunc)
|
||
{
|
||
using ValType = _TransformIterT<IT,FUN>::ResVal;
|
||
using TransIT = _TransformIterT<IT,FUN>::TransIter;
|
||
|
||
return IterSource<ValType>::build (
|
||
new WrappedLumieraIter<TransIT> (
|
||
transformIter (forward<IT>(source), processingFunc)));
|
||
}
|
||
|
||
|
||
/** @return a Lumiera Forward Iterator to yield
|
||
* all the keys of the given Map or Hashtable
|
||
*/
|
||
template<class MAP>
|
||
auto
|
||
eachMapKey (MAP& map)
|
||
{
|
||
using Range = RangeIter<typename MAP::iterator>;
|
||
|
||
Range contents (map.begin(), map.end());
|
||
return wrapIter (takePairFirst (contents));
|
||
}
|
||
|
||
|
||
/** @return a Lumiera Forward Iterator to yield
|
||
* all the values of the given Map or Hashtable
|
||
*/
|
||
template<class MAP>
|
||
_MapT<MAP>::ValIter
|
||
eachMapVal (MAP& map)
|
||
{
|
||
using Range = RangeIter<typename MAP::iterator>;
|
||
|
||
Range contents (map.begin(), map.end());
|
||
return wrapIter (takePairSecond(contents));
|
||
}
|
||
|
||
|
||
/** @return a Lumiera Forward Iterator to yield
|
||
* all \em distinct keys of a Multimap
|
||
* @warning we do a full table scan to find
|
||
* the distinct keys
|
||
*/
|
||
template<class MAP>
|
||
auto
|
||
eachDistinctKey (MAP& map)
|
||
{
|
||
using Range = RangeIter<typename MAP::iterator>;
|
||
|
||
Range contents (map.begin(), map.end());
|
||
return wrapIter (filterRepeat (takePairFirst(contents)));
|
||
}
|
||
|
||
|
||
/** @return a Lumiera Forward Iterator to yield all values
|
||
* associated to the given key within this Map or Multimap
|
||
* @note obviously in case of a Map we'll get at most one result.
|
||
*/
|
||
template<class MAP>
|
||
_MapT<MAP>::ValIter
|
||
eachValForKey (MAP& map, typename _MapT<MAP>::Key key)
|
||
{
|
||
using Pos = MAP::iterator;
|
||
using Range = RangeIter<Pos>;
|
||
|
||
std::pair<Pos,Pos> valuesForKey = map.equal_range(key);
|
||
Range contents (valuesForKey.first, valuesForKey.second);
|
||
return wrapIter (takePairSecond(contents));
|
||
}
|
||
|
||
|
||
/** @param container a STL-like container, providing
|
||
* - a typedef `iterator`
|
||
* - functions `begin()` and `end()`
|
||
* @return a Lumiera Forward Iterator yielding all values
|
||
* starting with \c begin and excluding \c end .
|
||
*/
|
||
template<class CON>
|
||
_SeqT<CON>::Iter
|
||
eachEntry (CON& container)
|
||
{
|
||
using ValType = _SeqT<CON>::Val;
|
||
using Range = RangeIter<typename CON::iterator>;
|
||
|
||
Range contents (container.begin(), container.end());
|
||
return IterSource<ValType>::build (new WrappedLumieraIter<Range>(contents));
|
||
}
|
||
|
||
|
||
/** @return a Lumiera Forward Iterator to yield all values
|
||
* defined by a classical Iterator range.
|
||
*/
|
||
template<class IT>
|
||
_RangeT<IT>::Iter
|
||
eachEntry (IT const& begin, IT const& end)
|
||
{
|
||
using ValType = _RangeT<IT>::Val;
|
||
using Range = RangeIter<IT>;
|
||
|
||
Range contents (begin, end);
|
||
return IterSource<ValType>::build (new WrappedLumieraIter<Range>(contents));
|
||
}
|
||
|
||
|
||
}} // namespace lib::iter_source
|
||
#endif
|