Well, finally. Lumiera Iterators were conceived as a concept from the very beginning, and this idea dates back even before C++11. Furthermore, over time some ''trait templates'' have been developed, to detect if some template argument might match the known pattern of functions associated with a Lumiera Iterator. Meanwhile this concept is used pervasively in the whole code base, and also the traits templates proved to be reliable. It therefore seems that the time is ripe to gradually introduce a genuine **concept** `lum_iter` I'll take a defensive approach here, since any mistake would lead to a massive breakage of compilation. The new concept is defined simply by delegating to to the tried-and-tested trait template, which is also renamed `can_IterForEach` ⟼ `can_LumieraIter` One immediate benefit is that the compiler is now able to select overloads based on that concept, which allows to drop the tricky selection of variants based on the SFINAE technique and `enable_if`. For validation, one very heavily used case of that pattern was switched in `lib/format-util.hpp', which notably lies in the code-path used by `util::join`
276 lines
7.4 KiB
C++
276 lines
7.4 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 <array>
|
||
#include <string>
|
||
#include <vector>
|
||
#include <sstream>
|
||
#include <utility>
|
||
#include <typeinfo>
|
||
|
||
|
||
|
||
namespace util {
|
||
|
||
using lib::meta::lum_iter;
|
||
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 = lib::meta::ValueTypeBinding<IT>::value_type;
|
||
|
||
return lib::transformIter (forward<IT>(src), util::toString<Val>);
|
||
}
|
||
|
||
|
||
|
||
namespace { // helper to build range iterator on demand
|
||
template<class CON>
|
||
struct _RangeIter
|
||
{
|
||
using StlIter = CON::const_iterator;
|
||
|
||
lib::RangeIter<StlIter> iter;
|
||
|
||
_RangeIter(CON const& collection)
|
||
: iter(begin(collection), end(collection))
|
||
{ }
|
||
};
|
||
|
||
template<lum_iter IT>
|
||
struct _RangeIter<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 COLL>
|
||
inline string
|
||
join (COLL&& coll, string const& delim =", ")
|
||
{
|
||
using Coll = lib::meta::Strip<COLL>::TypePlain;
|
||
_RangeIter<Coll> range(std::forward<COLL>(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...), ".");
|
||
}
|
||
|
||
|
||
|
||
/** one-argument variant that can be forward declared... */
|
||
template<class COLL>
|
||
inline string
|
||
toStringParen (COLL&& coll)
|
||
{
|
||
return "("+join (forward<COLL> (coll))+")";
|
||
}
|
||
|
||
template<class COLL>
|
||
inline string
|
||
toStringBracket (COLL&& coll)
|
||
{
|
||
return "["+join (forward<COLL> (coll))+"]";
|
||
}
|
||
|
||
|
||
/** convenient pretty-printer for std::array instances */
|
||
template<typename T, std::size_t N>
|
||
struct StringConv<std::array<T,N>>
|
||
{
|
||
static std::string
|
||
invoke (std::array<T,N> const& arr) noexcept
|
||
{
|
||
return util::toStringBracket (arr);
|
||
}
|
||
};
|
||
|
||
|
||
} // namespace util
|
||
#endif /*LIB_FORMAT_UTIL_H*/
|