DOC: document some of the language limitations highlighted by this research

This commit is contained in:
Fischlurch 2017-11-27 05:39:47 +01:00
parent d8f7a22123
commit 134821ca15
4 changed files with 95 additions and 40 deletions

View file

@ -222,6 +222,33 @@ Thus -- as of 9/2014 -- the _rules of the game_ are as folows
New Capabilities
----------------
As the support for the new language standard matures, we're able to extend our capabilities to write code
more succinctly and clearer to read. Sometimes we have to be careful in order to use these new powers the right way...
Variadic Templates
~~~~~~~~~~~~~~~~~~
Programming with variadic template arguments can be surprisingly tricky and the resulting code design tends to be confusing.
This is caused by the fact that a _»variadic argument pack«_ is not a type proper. Rather, it is a syntactic (not lexical)
macro. It is thus not possible to ``grab'' the type from variadic arguments and pass it directly to some metaprogramming
helper or metafunction. The only way to dissect and evaluate such types is kind of _going backwards_: pass them to some
helper function or template with explicit specialisations, thereby performing a pattern match.
A typical example: Let's assume we're about to write a function or constructor to accept flexible arguments, but we
want to ensure those arguments to conform to some standards. Maybe we can handle only a small selection of types
sensibly within the given context -- and thus we want to check and produce a compilation failure otherwise.
Unfortunately, this is not possible, at least not directly. If we want to get at the argument types one by one, we need
to fabricate some variadic template helper type, and then add an explicit specialisation to it, for the sole purpose of
writing a pattern match `<Arg a, Args...>`. Another common technique is to fabricate a `std::tuple<ARGS...>` and then
to use the `std::get<i> (std::tuple<ARGS...>)` to grab individual types. Such might be bewildering for the casual
reader, since we never intend to create a storage tuple anywhere.
NOTE: we might consider to reshape our typelist to be a real `lib::meta::Types<TYPES...>` and outfit it with some
of the most frequently needed accessors. At least the name ``Types'' is somewhat more indicative than ``tuple''.
Type inference
~~~~~~~~~~~~~~
Generally speaking, the use of `auto` and type inference has the potential to significantly improve readability of
@ -245,3 +272,19 @@ Forward Iterator concept. There are some notable pitfalls to consider, though
when just passing the result from a delegate function (or lambda!).
Generic Lambdas
~~~~~~~~~~~~~~~
A generic lambda (i.e. using `auto` on some argument) produces _a template_, not a regular `operator()` function.
Whenever such a lambda is invoked, this template is instantiated. This has several ramifications. For one, the
notorious techniques for detecting a function work well on conventional lambdas, but _fail on generic lambdas_.
And it is not possible to ``probe'' a template with SFINAE: If you instantiate a template, and the result is not
valid, you've produced a _compilation failure_, not a _substitution failure._ Which means, the compilation aborts,
instead of just trying the next argument substitution (as you'd might expect). Another consequence is that you
can not _just accept a generic lambda as argument and put it into a `std::function` (which otherwise is the
idiomatic way of ``just accepting anything function-like''). In order to place a generic lambda into a `std::function`,
you must decide upon the concrete argument and return type(s) and thus instantiate the template at that point.
The only remedy to get around these serious limitations will be when C++ starts to support *Concepts* -- which
can be expected to replace the usage of unbounded type inference in many situations: for example, it would then
be possible to accept just ``an Iterator'' or ``a STL container''...

View file

@ -266,6 +266,7 @@ namespace lib {
{ };
/** decide how to adapt and embed the source sequence into the resulting TreeExplorer */
template<class SRC, typename SEL=void>
struct _TreeExplorerTraits
{
@ -602,7 +603,12 @@ namespace lib {
/* ==== convenient builder free functions ==== */
/** start building a TreeExplorer
* by suitably wrapping the given iterable source.
* @return a TreeEplorer, which is an Iterator to yield all the source elements,
* but may also be used to build an processing pipeline.
*/
template<class IT>
inline auto
treeExplore (IT&& srcSeq)

View file

@ -34,7 +34,7 @@
** Similar to IterExplorer_test, the his test relies on a demonstration setup featuring
** a custom encapsulated state type: we rely on a counter with start and end value,
** embedded into an iterator. Basically, this running counter, when iterated, generates
** a sequence of numbers start ... end.
** a descending sequence of numbers start ... end.
** So -- conceptually -- this counting iterator can be thought to represent this
** sequence of numbers. Note that this is a kind of abstract or conceptual
** representation, not a factual representation of the sequence in memory.
@ -84,7 +84,7 @@ namespace test{
/**
* This iteration _"state core" type_ describes
* a sequence of numbers yet to be delivered.
* a descending sequence of numbers yet to be delivered.
*/
class CountDown
{
@ -119,8 +119,9 @@ namespace test{
/**
* A straight ascending number sequence as basic test iterator.
* The tests will dress up this source sequence in various ways.
* A straight descending number sequence as basic test iterator.
* It is built wrapping an opaque "state core" (of type CountDown).
* @note the "state core" is not accessible from the outside
*/
class NumberSequence
: public IterStateWrapper<uint, CountDown>
@ -138,9 +139,7 @@ namespace test{
/** Diagnostic helper: "squeeze out" the given iterator
* and join all the elements yielded into a string
*/
/** Diagnostic helper: join all the elements from a _copy_ of the iterator */
template<class II>
inline string
materialise (II&& ii)
@ -148,6 +147,7 @@ namespace test{
return util::join (std::forward<II> (ii), "-");
}
/** Diagnostic helper: "squeeze out" the given iterator until exhaustion */
template<class II>
inline void
pullOut (II & ii)
@ -183,17 +183,18 @@ namespace test{
* ## Explanation
*
* Both this test and the IterExplorer template might be bewildering
* and cryptic, unless you know the *Monad design pattern*. Monads are
* heavily used in functional programming, actually they originate
* from Category Theory. Basically, Monad is a pattern where we
* combine several computation steps in a specific way; but instead
* of intermingling the individual computation steps and their
* combination, the goal is to isolate and separate the _mechanics
* of combination_, so we can focus on the actual _computation steps:_
* The mechanics of combination are embedded into the Monad type,
* which acts as a kind of container, holding some entities
* to be processed. The actual processing steps are then
* fed to the monad as "function object" parameters.
* and cryptic, unless you know the *Monad design pattern*. »Monads«
* are heavily used in functional programming, actually they originate
* from Category Theory. Basically, Monad is a pattern where we combine
* several computation steps in a specific way; but instead of intermingling
* the individual computation steps and their combination, the goal is to
* isolate and separate the _mechanics of combination_, so we can focus on
* the actual _computation steps:_ The mechanics of combination are embedded
* into the Monad type, which acts as a kind of container, holding some entities
* to be processed. The actual processing steps are then attached to the monad as
* "function object" parameters. It is up to the monad to decide if, and when
* those processing steps are applied to the embedded values and how to combine
* the results into a new monad.
*
* Using the monad pattern is well suited when both the mechanics of
* combination and the individual computation steps tend to be complex.
@ -279,7 +280,7 @@ namespace test{
CHECK (materialise(ii) == "-2-3--5-8--13");
CHECK (materialise(jj) == "3--5-8--13");
// can adapt STL container automatically
// can even adapt STL container automatically
auto kk = treeExplore(numz);
CHECK (!isnil (kk));
CHECK (1 == *kk);
@ -325,17 +326,19 @@ namespace test{
void
verify_expandOperation()
{
/* == "monadic flatMap" == */
verify_treeExpandingIterator(
treeExplore(CountDown{5})
.expand([](uint j){ return CountDown{j-1}; })
.expand([](uint j){ return CountDown{j-1}; }) // expand-functor: Val > StateCore
);
verify_treeExpandingIterator(
treeExplore(CountDown{5})
.expand([](uint j){ return NumberSequence{j-1}; })
.expand([](uint j){ return NumberSequence{j-1}; }) // expand-functor: Val > Iter
);
// lambda with side-effect and a different return type
// lambda with side-effect and return type different from source iter
vector<vector<uint>> childBuffer;
auto expandIntoChildBuffer = [&](uint j) -> vector<uint>&
{
@ -348,35 +351,38 @@ namespace test{
verify_treeExpandingIterator(
treeExplore(CountDown{5})
.expand(expandIntoChildBuffer)
.expand(expandIntoChildBuffer) // expand-functor: Val > STL-container&
);
// test routine called the expansion functor five times
CHECK (5 == childBuffer.size());
/* == "state manipulation" use cases == */
verify_treeExpandingIterator(
treeExplore(CountDown{5})
.expand([](CountDown const& core){ return CountDown{ yield(core) - 1}; })
.expand([](CountDown const& core){ return CountDown{ yield(core) - 1}; }) // expand-functor: StateCore const& -> StateCore
);
verify_treeExpandingIterator(
treeExplore(CountDown{5})
.expand([](CountDown const& core){ return NumberSequence{ yield(core) - 1}; })
.expand([](CountDown core){ return NumberSequence{ yield(core) - 1}; }) // expand-functor: StateCore -> Iter
);
#if false /////////////////////////////////////////////////////////////////////////////////////////////////////////////TICKET #1118 : GDB Segfault on loading the inferior
/////////////////////////////////////////////////////////////////////////////////////////////////////////////TICKET #1118 : Generated code works just fine and passes Test though
#endif /////////////////////////////////////////////////////////////////////////////////////////////////////////////TICKET #1118 : GDB Segfault on loading the inferior
verify_treeExpandingIterator(
treeExplore(CountDown{5})
.expand([](auto & it){ return CountDown{ *it - 1}; })
.expand([](auto & it){ return CountDown{ *it - 1}; }) // generic Lambda: Iter& -> StateCore
);
verify_treeExpandingIterator(
treeExplore(CountDown{5})
.expand([](auto it){ return decltype(it){ *it - 1}; })
.expand([](auto it){ return decltype(it){ *it - 1}; }) // generic Lambda: Iter -> Iter
);
#endif /////////////////////////////////////////////////////////////////////////////////////////////////////////////TICKET #1118 : GDB Segfault on loading the inferior
}

View file

@ -5100,8 +5100,8 @@
</node>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1510941463562" ID="ID_845080696" MODIFIED="1510941523082" TEXT="TreeExplorer">
<icon BUILTIN="flag-yellow"/>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1511571446467" ID="ID_1778455053" MODIFIED="1511571478593" TEXT="expand-Operation">
<icon BUILTIN="pencil"/>
<node COLOR="#338800" CREATED="1511571446467" ID="ID_1778455053" MODIFIED="1511757459789" TEXT="expand-Operation">
<icon BUILTIN="button_ok"/>
<node COLOR="#338800" CREATED="1511227813712" ID="ID_1961708112" MODIFIED="1511228177906" TEXT="Fall-1">
<icon BUILTIN="button_ok"/>
<node CREATED="1511227821087" ID="ID_143800006" MODIFIED="1511227823932" TEXT="monadisch"/>
@ -5163,7 +5163,7 @@
<node CREATED="1511402855174" ID="ID_386377958" MODIFIED="1511402861001" TEXT="beides k&#xf6;nnen unterschiedliche Typen sein"/>
<node CREATED="1511402861613" ID="ID_1536434222" MODIFIED="1511402872072" TEXT="solange der expandFunktor beide akzeptiert...."/>
</node>
<node CREATED="1511455454190" ID="ID_1384978479" MODIFIED="1511455481991" TEXT="Problem: kein generischer Expand-Functor">
<node CREATED="1511455454190" FOLDED="true" ID="ID_1384978479" MODIFIED="1511757482192" TEXT="Problem: kein generischer Expand-Functor">
<icon BUILTIN="messagebox_warning"/>
<node CREATED="1511455484027" ID="ID_548359395" MODIFIED="1511572072779" TEXT="unmittelbare Folge davon, da&#xdf; wir den Typ des Expand-Funktors analysieren">
<richcontent TYPE="NOTE"><html>
@ -5313,17 +5313,17 @@
<node COLOR="#338800" CREATED="1511578319810" ID="ID_1918720180" MODIFIED="1511578585730" TEXT="unterst&#xfc;tzte Varianten dokumentieren">
<icon BUILTIN="button_ok"/>
</node>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1511578330448" ID="ID_1808038526" MODIFIED="1511578396263" TEXT="alle unterst&#xfc;tzten Varianten im Test abdecken">
<arrowlink COLOR="#b65d79" DESTINATION="ID_1298407683" ENDARROW="Default" ENDINCLINATION="-33;-51;" ID="Arrow_ID_1214344498" STARTARROW="None" STARTINCLINATION="-292;0;"/>
<icon BUILTIN="flag-yellow"/>
<node CREATED="1511578330448" ID="ID_1808038526" MODIFIED="1511757433857" TEXT="alle unterst&#xfc;tzten Varianten im Test abdecken">
<arrowlink COLOR="#5d68b6" DESTINATION="ID_1298407683" ENDARROW="Default" ENDINCLINATION="-33;-51;" ID="Arrow_ID_1214344498" STARTARROW="None" STARTINCLINATION="-292;0;"/>
<icon BUILTIN="button_ok"/>
</node>
</node>
</node>
</node>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1510941467586" ID="ID_381928532" MODIFIED="1510941523722" TEXT="TreeExplorer_test">
<icon BUILTIN="flag-yellow"/>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1510969031378" ID="ID_1678287752" MODIFIED="1510969040176" TEXT="verify_wrappedIterator();">
<icon BUILTIN="flag-yellow"/>
<node COLOR="#338800" CREATED="1510969031378" ID="ID_1678287752" MODIFIED="1511757396655" TEXT="verify_wrappedIterator();">
<icon BUILTIN="button_ok"/>
<node CREATED="1511054727547" ID="ID_328168347" MODIFIED="1511054858441" TEXT="Problem: mu&#xdf; Iterator-Arten unterscheiden">
<icon BUILTIN="messagebox_warning"/>
</node>
@ -5343,9 +5343,9 @@
<icon BUILTIN="button_ok"/>
</node>
</node>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1510969031379" ID="ID_1298407683" MODIFIED="1511578384759" TEXT="verify_expandOperation();">
<linktarget COLOR="#b65d79" DESTINATION="ID_1298407683" ENDARROW="Default" ENDINCLINATION="-33;-51;" ID="Arrow_ID_1214344498" SOURCE="ID_1808038526" STARTARROW="None" STARTINCLINATION="-292;0;"/>
<icon BUILTIN="flag-yellow"/>
<node COLOR="#338800" CREATED="1510969031379" ID="ID_1298407683" MODIFIED="1511757399431" TEXT="verify_expandOperation();">
<linktarget COLOR="#5d68b6" DESTINATION="ID_1298407683" ENDARROW="Default" ENDINCLINATION="-33;-51;" ID="Arrow_ID_1214344498" SOURCE="ID_1808038526" STARTARROW="None" STARTINCLINATION="-292;0;"/>
<icon BUILTIN="button_ok"/>
<node COLOR="#338800" CREATED="1511227813712" ID="ID_942570698" MODIFIED="1511228177906" TEXT="Fall-1">
<icon BUILTIN="button_ok"/>
<node CREATED="1511227821087" ID="ID_1644135845" MODIFIED="1511571551953" TEXT="monadisch / funktional">