Basically I am sick of writing for-loops in those cases
where the actual iteration is based on one or several data sources,
and I just need some damn index counter. Nothing against for-loops
in general — they have their valid uses — sometimes a for-loop is KISS
But in these typical cases, an iterator-based solution would be a
one-liner, when also exploiting the structured bindings of C++17
''I must admit that I want this for a loooooong time —''
...but always got intimidated again when thinking through the fine points.
Basically it „should be dead simple“ — as they say
Well — — it ''is'' simple, after getting the nasty aspects of tuple binding
and reference data types out of the way. Yesterday, while writing those
`TestFrame` test cases (which are again an example where you want to iterate
over two word sequences simultaneously and just compare them), I noticed that
last year I learned about the `std::apply`-to-fold-expression trick, and
that this solution pattern could be adapted to construct a tuple directly,
thereby circumventing most of the problems related to ''perfect forwarding''
So now we have a new util function `mapEach` (defined in `tuple-helper.hpp`)
and I have learned how to make this application completely generic.
As a second step, I implemented a proof-of-concept in `IterZip_test`,
which indeed was not really challenging, because the `IterExplorer`
is so very sophisticated by now and handles most cases with transparent
type-driven adaptors. A lot of work went into `IterExplorer` over the years,
and this pays off now.
The solution works as follows:
* apply the `lib::explore()` constructor function to the varargs
* package the resulting `IterExplorer` instantiations into a tuple
* build a »state core« implementation which just lifts out the three
iterator primitives onto this ''product type'' (i.e. the tuple)
* wrap it in yet another `IterExplorer`
* add a transformer function on top to extract a value-tuple for each ''yield'
As expected, works out-of-the-box, with all conceivable variants and wild
mixes of iterators, const, pointers, references, you name it....
PS: I changed the rendering of unsigned types in diagnostic output
to use the short notation, e.g. `uint` instead of `unsigned int`.
This dramatically improves the legibility of verification strings.
246 lines
6.9 KiB
C++
246 lines
6.9 KiB
C++
/*
|
||
FORMAT-UTIL.hpp - helpers for formatting and diagnostics
|
||
|
||
Copyright (C)
|
||
2009, 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 format-util.hpp
|
||
** Collection of small helpers and convenience shortcuts for diagnostics & formatting.
|
||
** - util::str() performs a failsafe to-String conversion, thereby preferring a
|
||
** built-in conversion operator, falling back to just a mangled type string.
|
||
** - util::join() generates an enumerating string from elements
|
||
** of an arbitrary sequence or iterable. Elements will be passed
|
||
** through our [generic string conversion](\ref util::toString)
|
||
**
|
||
** @see FormatHelper_test
|
||
** @see [frontend for boost::format, printf-style](\ref format-string.hpp)
|
||
**
|
||
*/
|
||
|
||
|
||
#ifndef LIB_FORMAT_UTIL_H
|
||
#define LIB_FORMAT_UTIL_H
|
||
|
||
#include "lib/meta/trait.hpp"
|
||
#include "lib/format-obj.hpp"
|
||
#include "lib/itertools.hpp"
|
||
#include "lib/symbol.hpp"
|
||
#include "lib/util.hpp"
|
||
|
||
#include <string>
|
||
#include <vector>
|
||
#include <sstream>
|
||
#include <utility>
|
||
#include <typeinfo>
|
||
|
||
|
||
|
||
namespace util {
|
||
|
||
using lib::meta::can_IterForEach;
|
||
using std::string;
|
||
using std::forward;
|
||
using std::move;
|
||
|
||
|
||
|
||
namespace { // helper to convert arbitrary elements toString
|
||
|
||
template<class CON>
|
||
inline void
|
||
do_stringify(CON&)
|
||
{ /* do nothing */ }
|
||
|
||
template<class CON, typename X, typename...ELMS>
|
||
inline void
|
||
do_stringify(CON& container, X const& elm, ELMS const& ...args)
|
||
{
|
||
container += util::toString (elm);
|
||
do_stringify (container, args...);
|
||
}
|
||
|
||
|
||
template<class CON, typename...ELMS>
|
||
struct SeqContainer
|
||
: CON
|
||
{
|
||
void
|
||
operator+= (string&& s)
|
||
{
|
||
CON::push_back (move(s));
|
||
}
|
||
};
|
||
|
||
// most common case: use a vector container...
|
||
using std::vector;
|
||
|
||
template<typename X, typename...ELMS>
|
||
struct SeqContainer<vector<X>, ELMS...>
|
||
:vector<X>
|
||
{
|
||
SeqContainer()
|
||
{
|
||
this->reserve(sizeof...(ELMS));
|
||
}
|
||
|
||
void
|
||
operator+= (string&& s)
|
||
{
|
||
this->emplace_back (move(s));
|
||
}
|
||
};
|
||
}//(end) stringify helper
|
||
|
||
|
||
/** convert a sequence of elements to string
|
||
* @param elms sequence of arbitrary elements
|
||
* @tparam CON the container type to collect the results
|
||
* @return a collection of type CON, initialised by the
|
||
* string representation of the given elements
|
||
*/
|
||
template<class CON, typename...ELMS>
|
||
inline CON
|
||
collectStr(ELMS const& ...elms)
|
||
{
|
||
SeqContainer<CON,ELMS...> storage;
|
||
do_stringify (storage, elms...);
|
||
return CON {move(storage)};
|
||
}
|
||
|
||
/** standard setup: convert to string into a vector */
|
||
template<typename...ELMS>
|
||
inline vector<string>
|
||
stringify (ELMS const& ...elms)
|
||
{
|
||
return collectStr<vector<string>> (elms...);
|
||
}
|
||
|
||
/** convert to string as transforming step in a pipeline
|
||
* @param src a "Lumiera Forward Iterator" with arbitrary result type
|
||
* @return a "Lumiera Forward Iterator" with string elements
|
||
* @see FormatHelper_test::checkStringify()
|
||
*/
|
||
template<class IT>
|
||
inline auto
|
||
stringify (IT&& src)
|
||
{
|
||
using Val = typename lib::meta::ValueTypeBinding<IT>::value_type;
|
||
|
||
return lib::transformIterator(forward<IT>(src), util::toString<Val>);
|
||
}
|
||
|
||
|
||
|
||
namespace { // helper to build range iterator on demand
|
||
template<class CON, typename TOGGLE = void>
|
||
struct _RangeIter
|
||
{
|
||
using StlIter = typename CON::const_iterator;
|
||
|
||
lib::RangeIter<StlIter> iter;
|
||
|
||
_RangeIter(CON const& collection)
|
||
: iter(begin(collection), end(collection))
|
||
{ }
|
||
};
|
||
|
||
template<class IT>
|
||
struct _RangeIter<IT, lib::meta::enable_if< can_IterForEach<IT>> >
|
||
{
|
||
IT iter;
|
||
|
||
_RangeIter(IT&& srcIter)
|
||
: iter(std::forward<IT>(srcIter))
|
||
{ }
|
||
_RangeIter(IT const& srcIter) // note: copy here
|
||
: iter(srcIter)
|
||
{ }
|
||
|
||
};
|
||
}//(end) join helper
|
||
|
||
|
||
/**
|
||
* enumerate a collection's contents, separated by delimiter.
|
||
* @param coll something that is standard- or Lumiera-iterable
|
||
* @note Lumiera-iterator is copied when given by ref, otherwise moved,
|
||
* while in all other cases the source container is taken by const&
|
||
* @return all contents converted to string and joined into
|
||
* a single string, with separators interspersed.
|
||
* @remarks based `ostringstream`; additionally, we use our
|
||
* [failsafe string conversion](\ref util::str),
|
||
* which in turn invokes custom string conversion,
|
||
* or lexical_cast as appropriate.
|
||
* @remarks alternatively, the `boost::join` library function
|
||
* could be used, which works on _arbitrary sequences_,
|
||
* which incurs some additional weight (both in terms
|
||
* of header include and debug code size). And failures
|
||
* on template substitution tend to be hard to understand,
|
||
* since this _generic sequence_ concept is just so danm
|
||
* absolutely generic (In fact that was the reason why I
|
||
* gave up and just rolled our own `join` utility)
|
||
*/
|
||
template<class CON>
|
||
inline string
|
||
join (CON&& coll, string const& delim =", ")
|
||
{
|
||
using Coll = typename lib::meta::Strip<CON>::TypePlain;
|
||
_RangeIter<Coll> range(std::forward<CON>(coll)); // copies when CON is reference
|
||
|
||
auto strings = stringify (std::move (range.iter));
|
||
if (!strings) return "";
|
||
|
||
std::ostringstream buffer;
|
||
for ( ; strings; ++strings)
|
||
buffer << *strings << delim;
|
||
|
||
// chop off last delimiter
|
||
size_t len = buffer.str().length();
|
||
ASSERT (len >= delim.length());
|
||
return buffer.str().substr(0, len - delim.length());
|
||
}
|
||
|
||
template<class X>
|
||
inline string
|
||
join (std::initializer_list<X> const&& ili, string const& delim =", ")
|
||
{
|
||
return join (ili, delim);
|
||
}
|
||
|
||
// Note: offering a variant of join with var-args would create lots of ambiguities
|
||
|
||
/** shortcut: List in parentheses, separated by comma, using temporary vector */
|
||
template<typename...ARGS>
|
||
inline string
|
||
joinArgList (ARGS const& ...args)
|
||
{
|
||
return "("+join (stringify (args...))+")";
|
||
}
|
||
|
||
/** shortcut: join directly with dashes */
|
||
template<typename...ARGS>
|
||
inline string
|
||
joinDash (ARGS const& ...args)
|
||
{
|
||
return join (stringify (args...), "-");
|
||
}
|
||
|
||
/** shortcut: join directly with dots */
|
||
template<typename...ARGS>
|
||
inline string
|
||
joinDot (ARGS const& ...args)
|
||
{
|
||
return join (stringify (args...), ".");
|
||
}
|
||
|
||
|
||
} // namespace util
|
||
#endif /*LIB_FORMAT_UTIL_H*/
|