diff --git a/doc/technical/code/c++11.txt b/doc/technical/code/c++11.txt index 26fd7eda4..850f9f4b9 100644 --- a/doc/technical/code/c++11.txt +++ b/doc/technical/code/c++11.txt @@ -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 ``. Another common technique is to fabricate a `std::tuple` and then +to use the `std::get (std::tuple)` 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` 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''... + diff --git a/src/lib/iter-tree-explorer.hpp b/src/lib/iter-tree-explorer.hpp index b716fa6e4..6b90402b6 100644 --- a/src/lib/iter-tree-explorer.hpp +++ b/src/lib/iter-tree-explorer.hpp @@ -266,6 +266,7 @@ namespace lib { { }; + /** decide how to adapt and embed the source sequence into the resulting TreeExplorer */ template 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 inline auto treeExplore (IT&& srcSeq) diff --git a/tests/library/iter-tree-explorer-test.cpp b/tests/library/iter-tree-explorer-test.cpp index fbf8c084a..6ae4b3085 100644 --- a/tests/library/iter-tree-explorer-test.cpp +++ b/tests/library/iter-tree-explorer-test.cpp @@ -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 @@ -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 inline string materialise (II&& ii) @@ -148,6 +147,7 @@ namespace test{ return util::join (std::forward (ii), "-"); } + /** Diagnostic helper: "squeeze out" the given iterator until exhaustion */ template 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> childBuffer; auto expandIntoChildBuffer = [&](uint j) -> vector& { @@ -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 } diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index 87fb3d6fd..bdfbbd202 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -5100,8 +5100,8 @@ - - + + @@ -5163,7 +5163,7 @@ - + @@ -5313,17 +5313,17 @@ - - - + + + - - + + @@ -5343,9 +5343,9 @@ - - - + + +