Library: introduce a concept for the »Lumiera Forward Iterator«

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`
This commit is contained in:
Fischlurch 2026-05-13 23:44:48 +02:00
parent d94bfe64b5
commit 3a491855e7
15 changed files with 76 additions and 242 deletions

View file

@ -100,7 +100,7 @@ namespace diff{
using std::__and_;
using std::__not_;
using lib::meta::enable_if;
using lib::meta::can_IterForEach;
using lib::meta::can_LumieraIter;
using lib::meta::can_STL_ForEach;
using DiffStep = TreeDiffLanguage::DiffStep;
@ -168,7 +168,7 @@ namespace diff{
* @note source iterator is copied into a heap allocated IterSource
*/
template<class IT>
MutationMessage(IT&& ii, enable_if< can_IterForEach<IT>, void*> =nullptr)
MutationMessage(IT&& ii, enable_if< can_LumieraIter<IT>, void*> =nullptr)
: _FrontEnd{iter_source::wrapIter (std::forward<IT>(ii))}
{ }
@ -179,7 +179,7 @@ namespace diff{
*/
template<class CON>
MutationMessage(CON& container, enable_if< __and_< can_STL_ForEach<CON>
,__not_<can_IterForEach<CON>>>, void*> =nullptr)
,__not_<can_LumieraIter<CON>>>, void*> =nullptr)
: _FrontEnd{iter_source::eachEntry(container)}
{ }

View file

@ -46,7 +46,7 @@
namespace util {
using lib::meta::can_IterForEach;
using lib::meta::lum_iter;
using std::string;
using std::forward;
using std::move;
@ -141,7 +141,7 @@ namespace util {
namespace { // helper to build range iterator on demand
template<class CON, typename TOGGLE = void>
template<class CON>
struct _RangeIter
{
using StlIter = CON::const_iterator;
@ -153,8 +153,8 @@ namespace util {
{ }
};
template<class IT>
struct _RangeIter<IT, lib::meta::enable_if< can_IterForEach<IT>> >
template<lum_iter IT>
struct _RangeIter<IT>
{
IT iter;

View file

@ -431,11 +431,14 @@ namespace iter_stl {
/** create empty snapshot */
IterSnapshot() { }
// standard copy operations acceptable
/** take snapshot by discharging the given Lumiera Forward iterator
* @warning depending on the implementation backing the source iterator,
* this might or might not yield side-effects.
*/
template<class IT>
template<class IT, typename =meta::disable_if_self<IterSnapshot, IT>>
IterSnapshot (IT&& src)
{
for ( ; src; ++src)
@ -446,7 +449,7 @@ namespace iter_stl {
* @warning depending on the implementation backing the source iterator,
* this might or might not yield side-effects.
*/
template<class IT>
template<class IT, typename =meta::disable_if_self<IterSnapshot, IT>>
IterSnapshot (IT const& src)
{
for (IT copy{src}; copy; ++copy)
@ -469,11 +472,6 @@ namespace iter_stl {
buffer_.emplace_back (*pos);
}
IterSnapshot(IterSnapshot &&) = default;
IterSnapshot(IterSnapshot const&) = default;
IterSnapshot& operator= (IterSnapshot const&) = default;
IterSnapshot& operator= (IterSnapshot &&) = default;
explicit
operator bool() const
{

View file

@ -193,7 +193,7 @@ namespace lib {
namespace iter {
using meta::is_StateCore;
using meta::can_IterForEach;
using meta::can_LumieraIter;
using meta::can_STL_ForEach;
using meta::ValueTypeBinding;
using meta::RefTraits;
@ -223,7 +223,7 @@ namespace lib {
template<class IT>
struct Trait
{
static constexpr bool is_LumieraIter = can_IterForEach<IT>::value;
static constexpr bool is_LumieraIter = meta::is_LumieraIter_v<IT>;
static constexpr bool is_STLIter = std::input_iterator<IT>;
static constexpr bool is_Legacy = legacy_traits_marked<IT>;
@ -579,7 +579,7 @@ namespace lib {
class IterStateCore
: public IT
{
static_assert (iter::can_IterForEach<IT>::value
static_assert (iter::can_LumieraIter<IT>::value
,"Lumiera Iterator required as source");
protected:
IT&

View file

@ -250,7 +250,7 @@ namespace lib {
using std::is_lvalue_reference;
using std::remove_reference_t;
using meta::is_StateCore;
using meta::can_IterForEach;
using meta::can_LumieraIter;
using meta::can_STL_ForEach;
using meta::ValueTypeBinding;
using meta::has_TypeResult;
@ -259,20 +259,20 @@ namespace lib {
template<class SRC>
struct shall_wrap_STL_Iter
: __and_<can_STL_ForEach<SRC>
,__not_<can_IterForEach<SRC>>
,__not_<can_LumieraIter<SRC>>
>
{ };
template<class SRC>
struct shall_use_Lumiera_Iter
: __and_<can_IterForEach<SRC>
: __and_<can_LumieraIter<SRC>
,__not_<is_StateCore<SRC>>
>
{ };
template<class SRC>
struct shall_use_StateCore
: __and_<__not_<can_IterForEach<SRC>>
: __and_<__not_<can_LumieraIter<SRC>>
,is_StateCore<SRC>
>
{ };
@ -361,12 +361,12 @@ namespace lib {
};
template<class SRC, typename RES>
struct _FlatteningTraits<SRC,RES, enable_if<__and_< is_lvalue_reference<RES>, can_IterForEach<RES> >>>
struct _FlatteningTraits<SRC,RES, enable_if<__and_< is_lvalue_reference<RES>, can_LumieraIter<RES> >>>
{
using SrcAdapter = meta::RefTraits<SRC>::value_type;
};
template<class SRC, typename RES>
struct _FlatteningTraits<SRC,RES, enable_if<__and_<__not_<is_lvalue_reference<RES>>, can_IterForEach<RES> >>>
struct _FlatteningTraits<SRC,RES, enable_if<__and_<__not_<is_lvalue_reference<RES>>, can_LumieraIter<RES> >>>
{
using NestedIter = meta::RefTraits<RES>::value_type;
using SrcAdapter = iter_explorer::WrapperAdapter<SRC,NestedIter>;
@ -612,7 +612,7 @@ namespace lib {
class RedressAdapter
: public SRC
{
static_assert(can_IterForEach<SRC>::value, "Lumiera Iterator required as source.");
static_assert(can_LumieraIter<SRC>::value, "Lumiera Iterator required as source.");
using SrcRes = iter::Yield<SRC>;
static_assert(meta::isLRef_v<SrcRes>, "Source iterator must yield its result by-ref.");
@ -678,7 +678,7 @@ namespace lib {
class Expander
: public SRC
{
static_assert(can_IterForEach<SRC>::value, "Lumiera Iterator required as source");
static_assert(can_LumieraIter<SRC>::value, "Lumiera Iterator required as source");
using _Trait = _ExpanderTraits<SRC,RES>;
using ResIter = _Trait::ResIter;
@ -897,10 +897,10 @@ namespace lib {
class Flattener
: public SRC
{
static_assert(can_IterForEach<SRC>::value, "Lumiera Iterator required as source");
static_assert(can_LumieraIter<SRC>::value, "Lumiera Iterator required as source");
using NestedIT = iter::Yield<SRC>;
static_assert(can_IterForEach<NestedIT>::value, "flatten() operation expects nested iterator");
static_assert(can_LumieraIter<NestedIT>::value, "flatten() operation expects nested iterator");
public:
using value_type = meta::ValueTypeBinding<NestedIT>::value_type;
@ -986,7 +986,7 @@ namespace lib {
class Transformer
: public SRC
{
static_assert(can_IterForEach<SRC>::value, "Lumiera Iterator required as source");
static_assert(can_LumieraIter<SRC>::value, "Lumiera Iterator required as source");
using TransformFunctor = function<RES(SRC&)>;
using TransformedItem = wrapper::ItemWrapper<RES>;
@ -1216,7 +1216,7 @@ namespace lib {
class Grouping
: public SRC
{
static_assert(can_IterForEach<SRC>::value, "Lumiera Iterator required as source");
static_assert(can_LumieraIter<SRC>::value, "Lumiera Iterator required as source");
protected:
using Group = std::array<RES, grp>;
@ -1350,7 +1350,7 @@ namespace lib {
class GroupAggregator
: public SRC
{
static_assert(can_IterForEach<SRC>::value, "Lumiera Iterator required as source");
static_assert(can_LumieraIter<SRC>::value, "Lumiera Iterator required as source");
protected:
using SrcValue = meta::ValueTypeBinding<SRC>::value_type;
@ -1439,7 +1439,7 @@ namespace lib {
class Filter
: public SRC
{
static_assert(can_IterForEach<SRC>::value, "Lumiera Iterator required as source");
static_assert(can_LumieraIter<SRC>::value, "Lumiera Iterator required as source");
protected:
using FilterPredicate = function<bool(SRC&)>;
@ -1540,7 +1540,7 @@ namespace lib {
: public Filter<SRC>
{
using _Filter = Filter<SRC>;
static_assert(can_IterForEach<SRC>::value, "Lumiera Iterator required as source");
static_assert(can_LumieraIter<SRC>::value, "Lumiera Iterator required as source");
public:
MutableFilter() =default;
@ -1705,7 +1705,7 @@ namespace lib {
class StopTrigger
: public IterStateCore<SRC>
{
static_assert(can_IterForEach<SRC>::value, "Lumiera Iterator required as source");
static_assert(can_LumieraIter<SRC>::value, "Lumiera Iterator required as source");
using Core = IterStateCore<SRC>;
using Cond = function<bool(SRC&)>;
@ -1905,7 +1905,7 @@ namespace lib {
class IterExplorer
: public SRC
{
static_assert(can_IterForEach<SRC>::value, "Lumiera Iterator required as source");
static_assert(can_LumieraIter<SRC>::value, "Lumiera Iterator required as source");
public:

View file

@ -405,7 +405,7 @@ namespace lib {
class ValueAdapter
: public SRC
{
static_assert (can_IterForEach<SRC>::value, "Lumiera Iterator required as source.");
static_assert (can_LumieraIter<SRC>::value, "Lumiera Iterator required as source.");
public:
SRC::value_type

View file

@ -506,7 +506,7 @@ namespace meta {
* @see IterableClassification_test
*/
template<typename T>
class can_IterForEach
class can_LumieraIter
{
using Type = Strip<T>::TypeReferred;
@ -524,6 +524,12 @@ namespace meta {
};
};
template<class IT>
constexpr bool is_LumieraIter_v = meta::can_LumieraIter<IT>::value;
template<class IT>
concept lum_iter = is_LumieraIter_v<IT>;
/** Trait template to detect a type exposing a »state core« API.
@ -531,10 +537,10 @@ namespace meta {
* with the help of lib::IterStateWrapper or lib::IterableDecorator.
* This check is heuristic, based on the presence of function names.
*/
template<typename T>
template<typename COR>
class is_StateCore
{
using Type = Strip<T>::TypeReferred;
using Type = Strip<COR>::TypeReferred;
META_DETECT_FUNCTION_ARGLESS(checkPoint);
META_DETECT_FUNCTION_ARGLESS(iterNext);
@ -547,10 +553,19 @@ namespace meta {
};
};
template<class COR>
constexpr bool is_StateCore_v = is_StateCore<COR>::value;
template<class COR>
concept state_core = is_StateCore_v<COR>;
/** Trait template to detect a type usable with the STL for-each loop.
* Basically we're looking for the functions to get the begin/end iterator
* @remark many Lumiera Iterators define those additional functions any
* can thus be used in a for-each loop, since a default-constructed
* Lumiera Iterator can act as an "end" sentinel.
*/
template<typename T>
class can_STL_ForEach

View file

@ -1,181 +0,0 @@
/*
TUPLE-ACCESSOR.hpp - metaprogramming utilities for type and data tuples
Copyright (C)
2016 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 tuple-accessor.hpp
** Helper templates to access parts of a tuple.
**
** @todo 2026 this is old metaprogramming code, largely obsoleted by newer features of the C++ language.
** However, since this code is still used in some basic support facilities, it was separated out,
** so that it can be refactored as time permits.
**
** @see TupleHelper_test
**
*/
#ifndef LIB_META_TUPLE_ACCESSOR_H
#define LIB_META_TUPLE_ACCESSOR_H
#include "lib/meta/tuple-helper.hpp"
#include "lib/format-obj.hpp"
#include <string>
namespace lib {
namespace meta {
/**
* Decorating a tuple type with auxiliary data access operations.
* This helper template builds up a subclass of the given TUP (base) type
* (which is assumed to be a Tuple or at least need to be copy constructible
* from `Tuple<TYPES>` ). The purpose is to use the Tuple as storage record, but
* to add a layer of access functions, which in turn might rely on the exact
* type of the individual elements within the Tuple. To achieve this, for each
* type within the Tuple, the TUP type is decorated with an instance of the
* template passed in as template template parameter _X_. Each of these
* decorating instances is provided with an index number, allowing to
* access "his" specific element within the underlying tuple.
*
* The decorating template _X_ need to take its own base class as template
* parameter. Typically, operations on _X_ will be defined in a recursive fashion,
* calling down into this templated base class. To support this, an instantiation
* of _X_ with the empty type sequence is generated for detecting recursion end
* (built as innermost decorator, i.e. the immediate subclass of TUP)
*/
template
< template<class,class,class, uint> class _X_ ///< user provided template<Type, Base, TupleType, arg-idx>
, typename TYPES ///< Sequence of types to use within the Accessor
, class TUP =Tuple<TYPES> ///< the tuple type to build on
, uint i = 0 ///< tuple element index counter
>
class BuildTupleAccessor
{
// prepare recursion...
using Head = Split<TYPES>::Head;
using Tail = Split<TYPES>::Tail;
using NextBuilder = BuildTupleAccessor<_X_, Tail,TUP, i+1>;
using NextAccessor = NextBuilder::Product;
public:
/** type of the product created by this template.
* Will be a subclass of TUP */
using Product = _X_< Head // the type to use for this accessor
, NextAccessor // the base type to inherit from
, TUP // the tuple type we build upon
, i // current element index
>;
};
template
< template<class,class,class, uint> class _X_
, class TUP
, uint i
>
class BuildTupleAccessor< _X_, Types<>, TUP, i>
{
public:
using Product = _X_<Nil, TUP, TUP, i>; // Note: i == tuple size
};
/**
* Helper to dump tuple contents.
* Defined to act as "Accessor" for BuildTupleAccessor, this helper template
* allows to create a recursive operation to invoke string conversion on
* all elements within any given tuple.
*/
template
< typename TY
, class BASE
, class TUP
, uint idx
>
struct TupleElementDisplayer
: BASE
{
using BASE::BASE;
std::string
dump (std::string const& prefix ="(") const
{
return BASE::dump (prefix + util::toString(std::get<idx>(*this))+",");
}
};
template<class TUP, uint n>
struct TupleElementDisplayer<Nil, TUP, TUP, n>
: TUP
{
TupleElementDisplayer (TUP const& tup)
: TUP(tup)
{ }
std::string
dump (std::string const& prefix ="(") const
{
if (1 < prefix.length())
// remove the trailing comma
return prefix.substr (0, prefix.length()-1) +")";
else
return prefix+")";
}
};
/**
* convenience function to dump a given tuple's contents.
* Using the BuildTupleAccessor, we layer a stack of Instantiations of
* the TupleElementDisplayer temporarily on top of the given tuple,
* just to invoke a recursive call chain through these layers
* and get a string representation of each element in the
* tuple.
*/
template<typename...TYPES>
inline std::string
dump (std::tuple<TYPES...> const& tuple)
{
using BuildAccessor = BuildTupleAccessor<TupleElementDisplayer, Types<TYPES...>>;
using Displayer = BuildAccessor::Product ;
return static_cast<Displayer const&> (tuple)
.dump();
}
}} // namespace lib::meta
// add a specialisation to enable tuple string conversion
namespace util {
template<typename...TYPES>
struct StringConv<std::tuple<TYPES...>>
{
static std::string
invoke (std::tuple<TYPES...> const& tuple) noexcept
try {
return "«"+typeStr(tuple)
+ "»──" + lib::meta::dump (tuple);
}
catch(...) { return FAILURE_INDICATOR; }
};
} // namespace util
#endif /*LIB_META_TUPLE_ACCESSOR_H*/

View file

@ -70,6 +70,7 @@
#include <tuple>
#include <utility>
#include <sstream>
#include <functional>

View file

@ -57,14 +57,14 @@ namespace util {
typedef lib::meta::Unwrap<T>::Type TaT;
enum{ value = lib::meta::can_STL_ForEach<TaT>::value
&&!lib::meta::can_IterForEach<T>::value
&&!lib::meta::can_LumieraIter<T>::value
};
};
template<typename T>
struct treat_as_LumieraIterator
{
enum{ value = lib::meta::can_IterForEach<T>::value
enum{ value = lib::meta::can_LumieraIter<T>::value
};
};

View file

@ -56,7 +56,7 @@ namespace util {
using lib::meta::disable_if;
using lib::meta::can_STL_ForEach;
using lib::meta::can_IterForEach;
using lib::meta::can_LumieraIter;
@ -108,7 +108,7 @@ namespace util {
template <typename Container
,typename FUN
>
inline disable_if< can_IterForEach<Container>,
inline disable_if< can_LumieraIter<Container>,
FUN >
for_each (Container const& coll, FUN doIt)
{
@ -124,7 +124,7 @@ namespace util {
template <typename IT
,typename FUN
>
inline enable_if< can_IterForEach<IT>,
inline enable_if< can_LumieraIter<IT>,
FUN >
for_each (IT const& ii, FUN doIt)
{
@ -152,7 +152,7 @@ namespace util {
template <typename IT
,typename FUN
>
inline enable_if< can_IterForEach<IT>,
inline enable_if< can_LumieraIter<IT>,
bool >
and_all (IT const& ii, FUN predicate)
{
@ -179,7 +179,7 @@ namespace util {
template <typename IT
,typename FUN
>
inline enable_if< can_IterForEach<IT>,
inline enable_if< can_LumieraIter<IT>,
bool >
has_any (IT const& ii, FUN predicate)
{

View file

@ -20,6 +20,7 @@
#include "test/run.hpp"
#include "lib/iter-zip.hpp"
#include "lib/iter-explorer.hpp"
#include "lib/meta/tuple-helper.hpp"
#include "test/test-helper.hpp"
#include "lib/format-util.hpp"
#include "lib/util.hpp"

View file

@ -131,20 +131,20 @@ namespace test {
CHECK (false == can_STL_ForEach<LumieraCoreIter>::value );
// detect Lumiera Forward Iterator
CHECK (false == can_IterForEach<TimeVector>::value );
CHECK (false == can_IterForEach<LongSet> ::value );
CHECK (false == can_IterForEach<CharMap> ::value );
CHECK (false == can_IterForEach<BoolList> ::value );
CHECK (false == can_IterForEach<ShortDeque>::value );
CHECK (false == can_IterForEach<CustomCont>::value );
CHECK (false == can_LumieraIter<TimeVector>::value );
CHECK (false == can_LumieraIter<LongSet> ::value );
CHECK (false == can_LumieraIter<CharMap> ::value );
CHECK (false == can_LumieraIter<BoolList> ::value );
CHECK (false == can_LumieraIter<ShortDeque>::value );
CHECK (false == can_LumieraIter<CustomCont>::value );
CHECK ( true == can_IterForEach<LumieraRangeIter> ::value );
CHECK ( true == can_IterForEach<LumieraFilteredIter> ::value );
CHECK ( true == can_IterForEach<LumieraTransformedIter>::value );
CHECK ( true == can_IterForEach<CustomLumieraIter> ::value );
CHECK ( true == can_LumieraIter<LumieraRangeIter> ::value );
CHECK ( true == can_LumieraIter<LumieraFilteredIter> ::value );
CHECK ( true == can_LumieraIter<LumieraTransformedIter>::value );
CHECK ( true == can_LumieraIter<CustomLumieraIter> ::value );
CHECK (false == can_IterForEach<CustomStateCore>::value );
CHECK ( true == can_IterForEach<LumieraCoreIter>::value ); // Note: Lumiera iterator capability added as wrapper
CHECK (false == can_LumieraIter<CustomStateCore>::value );
CHECK ( true == can_LumieraIter<LumieraCoreIter>::value ); // Note: Lumiera iterator capability added as wrapper
// detect State Core
CHECK (false == is_StateCore<TimeVector>::value );

View file

@ -283,7 +283,7 @@ namespace test{
CHECK (horror.isParseFail());
// CsvParser is a »Lumiera Forward Iterator«
CHECK (meta::can_IterForEach<CsvParser>::value);
CHECK (meta::lum_iter<CsvParser>);
}

View file

@ -33,7 +33,7 @@ using util::first;
using util::last;
using lib::meta::can_STL_ForEach;
using lib::meta::can_IterForEach;
using lib::meta::can_LumieraIter;
using lib::meta::can_STL_backIteration;
using boost::lexical_cast;
@ -131,8 +131,8 @@ namespace test {
CHECK (!can_STL_ForEach<RangeI>::value);
CHECK (!can_STL_backIteration<RangeI>::value);
CHECK (!can_IterForEach<VecI>::value);
CHECK ( can_IterForEach<RangeI>::value);
CHECK (!can_LumieraIter<VecI>::value);
CHECK ( can_LumieraIter<RangeI>::value);
}
};