From 84377102322b128a710cc19617f68f583adc314b Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Wed, 25 Feb 2026 23:20:26 +0100 Subject: [PATCH] BufferProvider: research how to build a policy with callbacks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit While it is trivially simple to define some struct with callback functions, such a definition turns out to be obnoxiously complex and hard to read in pracice, thereby nullifying any benefit from the shorthand λ-based notation. This kind of problem with the readability at client-side is quite common when using callbacks for a policy-based design. To overcome this difficutly, it would be necessary to define the policy and the callbacks in one succinct builder expression. Unfortunately such a builder syntax typically requires to write a lot of boilerplate template code. After several failed experiments, I have found a bold new way to exploit features of C++20 in conjunction with a macro, allowing to generate a suitable data record directly form an C++ expression. --- src/lib/meta/maybe-compare.hpp | 56 ------ src/lib/meta/prop-builder.hpp | 89 ++++++++ src/lib/meta/trait.hpp | 22 +- src/lib/scoped-collection.hpp | 2 +- src/lib/several-builder.hpp | 6 +- src/lib/thread.hpp | 2 +- src/stage/interact/ui-location-solver.hpp | 2 +- src/stage/model/zoom-metric.hpp | 2 +- src/steam/control/command-op-closure.hpp | 1 - src/steam/control/memento-tie.hpp | 2 - tests/12metaprogramming.tests | 5 + tests/library/hetero-data-test.cpp | 8 +- tests/library/meta/prop-builder-test.cpp | 235 ++++++++++++++++++++++ wiki/thinkPad.ichthyo.mm | 207 +++++++++++++++---- 14 files changed, 518 insertions(+), 121 deletions(-) delete mode 100644 src/lib/meta/maybe-compare.hpp create mode 100644 src/lib/meta/prop-builder.hpp create mode 100644 tests/library/meta/prop-builder-test.cpp diff --git a/src/lib/meta/maybe-compare.hpp b/src/lib/meta/maybe-compare.hpp deleted file mode 100644 index dfe87c20c..000000000 --- a/src/lib/meta/maybe-compare.hpp +++ /dev/null @@ -1,56 +0,0 @@ -/* - MAYBE-COMPARE.hpp - guarded invocation of comparisons - - Copyright (C) - 2009, Hermann Vosseler - -  **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 maybe-compare.hpp - ** helpers for fail-safe invocation of comparison operations from generic code. - */ - - -#ifndef LIB_META_MAYBE_COMPARE_H -#define LIB_META_MAYBE_COMPARE_H - - - - -namespace lib { -namespace meta{ - - - /** - * Trait template for invoking equality comparison. - * This allows to treat some types specifically.... - */ - template - struct Comparator - { - static bool - equals (X const& x1, X const& x2) - { - return x1 == x2; - } - }; - - /* == add specialisations here == */ - - - template - inline bool - equals_safeInvoke (X const& x1, X const& x2) - { - return Comparator::equals(x1,x2); - } - - -}} // namespace lib::meta -#endif diff --git a/src/lib/meta/prop-builder.hpp b/src/lib/meta/prop-builder.hpp new file mode 100644 index 000000000..acbaf0abc --- /dev/null +++ b/src/lib/meta/prop-builder.hpp @@ -0,0 +1,89 @@ +/* + PROP-BUILDER.hpp - generate data record with arbitrary named fields + + Copyright (C) + 2026, Hermann Vosseler + +  **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 prop-builder.hpp + ** A builder for compile-time generation of data records with arbitrary named fields. + */ + + +#ifndef LIB_META_PROP_BUILDER_H +#define LIB_META_PROP_BUILDER_H + + +#include "lib/meta/trait.hpp" + +#include + + +namespace lib { +namespace meta{ + + using std::move; + using std::forward; + + + /** + * Anchor record and builder mechanics for generating data records. + * @see PropBuilder_test::simpleUsage + */ + struct PropBuilder + { + /** + * Core builder operation: invoke a builder-λ to extend »this« + * @param layerBilder a specially crafted invokable, that takes _this object_ + * and the given property value and creates an instance of a derived class. + * @note the macro \ref PROP_FIELD below will generate a suitable argument. + * @param val an arbitrary (movable) data element to be stored as property. + * @remark C++20 introduced the syntax for _explicit object member functions_, + * which allow to bind the "self" object instance to an explicitly defined + * first function parameter. Since this builder operation will be invoked + * on the generated compound object, the template parameter \a BAS will + * denote this compound, and the next builder-λ will derive from it. + */ + template + auto + define (this BAS&& chain, LAY const& layerBuilder, VAL&& val) + { + return layerBuilder (move(chain), forward(val)); + } + }; + + +#define PROP_FIELD(FIELD_NAME) \ + [] \ + (BAS&& bas, VAL&& val) \ + { \ + using BaseLayer = std::decay_t;\ + using Property = std::decay_t; \ + \ + struct DataLayer_ ## FIELD_NAME \ + : BaseLayer \ + { \ + Property FIELD_NAME; \ + \ + DataLayer_ ## FIELD_NAME (BaseLayer b \ + ,Property v) \ + : BaseLayer{move(b)} \ + ,FIELD_NAME{move(v)} \ + { } \ + }; \ + return DataLayer_ ## FIELD_NAME {forward(bas)\ + ,forward(val) \ + }; \ + } + + + +}} // namespace lib::meta +#endif /*LIB_META_PROP_BUILDER_H*/ diff --git a/src/lib/meta/trait.hpp b/src/lib/meta/trait.hpp index 7ab1ff8a6..b4ef8b71a 100644 --- a/src/lib/meta/trait.hpp +++ b/src/lib/meta/trait.hpp @@ -57,9 +57,6 @@ //Forward declarations for the Unwrap helper.... -namespace boost{ - template class reference_wrapper; -} namespace std { template class reference_wrapper; template class shared_ptr; @@ -159,19 +156,6 @@ namespace meta { } }; - template - struct Unwrap> - : std::true_type - { - using Type = X; - - static X& - extract (boost::reference_wrapper wrapped) - { - return wrapped; - } - }; - template struct Unwrap> : std::true_type @@ -328,7 +312,11 @@ namespace meta { , is_same > { }; - + + template + inline constexpr bool is_Subclass_v = is_Subclass(); + + /** compare for unadorned base type, disregarding const and references */ template struct is_basically diff --git a/src/lib/scoped-collection.hpp b/src/lib/scoped-collection.hpp index 8d4d9d82d..bc23b3033 100644 --- a/src/lib/scoped-collection.hpp +++ b/src/lib/scoped-collection.hpp @@ -130,7 +130,7 @@ namespace lib { TY& create (ARGS&& ...args) { - static_assert ( meta::is_Subclass() + static_assert ( meta::is_Subclass_v && sizeof(TY) <= siz, "ElementHolder buffer too small"); diff --git a/src/lib/several-builder.hpp b/src/lib/several-builder.hpp index 9bc5f6795..ea86d26f8 100644 --- a/src/lib/several-builder.hpp +++ b/src/lib/several-builder.hpp @@ -137,7 +137,7 @@ namespace lib { using std::is_volatile_v; using std::is_const_v; using std::is_same_v; - using lib::meta::is_Subclass; + using lib::meta::is_Subclass_v; using several::ArrayBucket; @@ -743,7 +743,7 @@ namespace lib { typename Policy::Fac& factory(*this); - if (is_Subclass() and has_virtual_destructor_v) + if (is_Subclass_v and has_virtual_destructor_v) { __ensureMark (VIRTUAL); return [factory](ArrayBucket* bucket){ unConst(factory).template destroy (bucket); }; @@ -753,7 +753,7 @@ namespace lib { __ensureMark (TRIVIAL); return [factory](ArrayBucket* bucket){ unConst(factory).template destroy (bucket); }; } - if (is_same_v and is_Subclass()) + if (is_same_v and is_Subclass_v) { __ensureMark (ELEMENT); return [factory](ArrayBucket* bucket){ unConst(factory).template destroy (bucket); }; diff --git a/src/lib/thread.hpp b/src/lib/thread.hpp index abfbdfc7f..107c9102e 100644 --- a/src/lib/thread.hpp +++ b/src/lib/thread.hpp @@ -741,7 +741,7 @@ namespace lib { inline void launchDetached (ThreadHookable::Launch&& launchBuilder) { - static_assert (lib::meta::is_Subclass()); + static_assert (lib::meta::is_Subclass_v); new TAR{move(launchBuilder) .atExit([](TAR& selfAllocation) diff --git a/src/stage/interact/ui-location-solver.hpp b/src/stage/interact/ui-location-solver.hpp index 3312ed199..68d0e49af 100644 --- a/src/stage/interact/ui-location-solver.hpp +++ b/src/stage/interact/ui-location-solver.hpp @@ -277,7 +277,7 @@ namespace interact { // append ID of the new element to be created // unless it's already there (and thus exists) resolver.append (elementTypeID); - return move (resolver); //////////////////////////////////////////////////////////////////TICKET #1402 : need a better solution for the builder-terminal-op. (it collides with the templated UICoord ctor) |NOTE: the compiler waring is wrong here, the move() is essential + return move (resolver); //////////////////////////////////////////////////////////////////TICKET #1402 : need a better solution for the builder-terminal-op. (it collides with the templated UICoord ctor) |NOTE: the compiler warning is wrong here, the move() is essential // use the first suitable solution and exit } else diff --git a/src/stage/model/zoom-metric.hpp b/src/stage/model/zoom-metric.hpp index 27b6b8a09..c8d11544a 100644 --- a/src/stage/model/zoom-metric.hpp +++ b/src/stage/model/zoom-metric.hpp @@ -64,7 +64,7 @@ namespace model { class ZoomMetric : public I { - static_assert(lib::meta::is_Subclass()); + static_assert(lib::meta::is_Subclass_v); protected: ZoomWindow zoomWindow_; diff --git a/src/steam/control/command-op-closure.hpp b/src/steam/control/command-op-closure.hpp index fb8e676ca..6ade2f271 100644 --- a/src/steam/control/command-op-closure.hpp +++ b/src/steam/control/command-op-closure.hpp @@ -31,7 +31,6 @@ #include "lib/meta/function-closure.hpp" #include "lib/meta/tuple-helper.hpp" #include "lib/meta/tuple-record-init.hpp" -#include "lib/meta/maybe-compare.hpp" #include "steam/control/command-closure.hpp" #include "steam/control/argument-erasure.hpp" #include "lib/typed-allocation-manager.hpp" diff --git a/src/steam/control/memento-tie.hpp b/src/steam/control/memento-tie.hpp index 0c5fc7272..f8d4d9d7c 100644 --- a/src/steam/control/memento-tie.hpp +++ b/src/steam/control/memento-tie.hpp @@ -31,7 +31,6 @@ #ifndef CONTROL_MEMENTO_TIE_H #define CONTROL_MEMENTO_TIE_H -#include "lib/meta/maybe-compare.hpp" #include "lib/meta/function-closure.hpp" #include "steam/control/command-signature.hpp" #include "lib/item-wrapper.hpp" @@ -48,7 +47,6 @@ namespace control { using lib::meta::func::bindLast; using lib::meta::func::chained; - using lib::meta::equals_safeInvoke; using lib::wrapper::ItemWrapper; diff --git a/tests/12metaprogramming.tests b/tests/12metaprogramming.tests index ee1f4fde0..0a977d29d 100644 --- a/tests/12metaprogramming.tests +++ b/tests/12metaprogramming.tests @@ -296,6 +296,11 @@ return: 0 END +TEST "Generated data records" PropBuilder_test <; - CHECK ((is_Subclass>())); + CHECK ((is_Subclass_v>)); auto b1 = Block1::build (42, 1.61803); CHECK (1.61803 == b1.get<1>()); @@ -109,11 +109,11 @@ namespace test{ verify_ChainBlock() { using Block1 = HeteroData; - CHECK ((is_Subclass>())); + CHECK ((is_Subclass_v>)); using Constructor = Block1::Chain; using Block2 = Constructor::NewFrame; - CHECK ((is_Subclass>())); + CHECK ((is_Subclass_v>)); auto b1 = Block1::build (41); auto b2 = Constructor::build (1.61, "Φ"); diff --git a/tests/library/meta/prop-builder-test.cpp b/tests/library/meta/prop-builder-test.cpp new file mode 100644 index 000000000..ae548605d --- /dev/null +++ b/tests/library/meta/prop-builder-test.cpp @@ -0,0 +1,235 @@ +/* + PropBuilder(Test) - verify helpers for working with tuples and type sequences + + Copyright (C) + 2026, Hermann Vosseler + +  **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 prop-builder-test.cpp + ** Demonstration and explanation of compile-time generated data records. + ** + */ + + +#include "lib/test/run.hpp" +#include "lib/test/test-helper.hpp" +#include "lib/meta/prop-builder.hpp" +//#include "lib/meta/tuple-helper.hpp" +//#include "meta/typelist-diagnostics.hpp" +//#include "meta/tuple-diagnostics.hpp" +//#include "lib/format-string.hpp" +//#include "lib/format-cout.hpp" +//#include "lib/hetero-data.hpp" +#include "lib/test/diagnostic-output.hpp"////////////////TODO + +#include + +using std::string; +using lib::test::showType; +//using lib::test::showSizeof; +//using util::toString; +//using util::_Fmt; +//using std::is_same_v; +//using std::make_tuple; +//using std::get; + +namespace lib { +namespace meta { +namespace test { + + + + namespace { // test data + + + } // (End) test data + + #define TYPE(_TY_) showType() + + + + /*********************************************************************//** + * @test Demonstrate how data records with arbitrary, named fields + * can be generated at compile time, using C++20 features. + * - explain the underlying mechanics + * - show a usage scenario + */ + class PropBuilder_test : public Test + { + virtual void + run (Arg) + { + simpleUsage(); + explain_building_blocks(); + demonstrate_usage_scenario(); + } + + + /** @test show syntax and usage + */ + void + simpleUsage() + { + auto record = PropBuilder() + .define (PROP_FIELD(one), 11u) + .define (PROP_FIELD(two), "2") + ; + + CHECK (record.one == 11u); + CHECK (record.two == "2"_expect); + + CHECK (TYPE(record.one) == "uint"_expect); + CHECK (TYPE(record.two) == "const char *"_expect); + + record.one = 55; + CHECK (record.one == 55u); + + auto copy = record; + copy.two = "something different"; + CHECK (record.two == "2"_expect); + CHECK (copy.two == "something different"_expect); + + } + + + struct Base + { + template + string + showSelfType (this X&) + { + return showType(); + } + }; + + struct Derived : Base { }; + + + /** @test demonstrate and verify the language features + * used to generate arbitrary record data types. + * - capture self-type with C++20 _explicit object member function_ + * - use generic-λ to generate a derived class with a property field + */ + void + explain_building_blocks() + { + // The first building block relies on the ability to pick up at invocation time + // the actual type that is used to call a member function, even while this function + // was defined somewhere in a base class: C++20 introduced a special syntax to mark + // the first "this" argument, passed automatically to each member invocation. + // And since code from a function template is generated late only, on actual use, + // we can define a type parameter to capture the "self type" explicitly: + + Derived derived; + Base& baseref{derived}; + + CHECK (baseref.showSelfType() == "test::PropBuilder_test::Base"_expect ); + CHECK (derived.showSelfType() == "test::PropBuilder_test::Derived"_expect ); + + + // The next building block exploits the fact that C++ allows to define + // a struct or class locally, within a function scope; and while this + // local type can be _referred to_ only locally, within this function, + // it is _not a hidden type_. Data elements using such a local type + // can be returned by-value — which allows us to "drop off" such + // a data record, and this also implicitly its type. + + // While this seems rather pointless in itself, it can be leveraged + // with the help of C++20 explicit lambda template parameters: we can + // move an instance of a base class into a function, thereby pick up + // its type, and define a new derived class, and even move the given + // base class instance into the embedded base class sub-object: + + auto dropperLambda = [](BAS&& bas, VAL&& val) + { + using BaseLayer = std::decay_t; + using Property = std::decay_t; + + struct DataLayer_somthing + : BaseLayer + { + Property something; + + DataLayer_somthing (BaseLayer b, Property v) + : BaseLayer{move(b)} + , something{move(v)} + { } + + DataLayer_somthing() = default; + }; + return DataLayer_somthing{forward(bas) + ,forward(val) + }; + }; + + struct SomeRecord + { + float someData; + }; + + ushort evil = 55; + auto compoundRecord = dropperLambda (SomeRecord{0.13}, evil); + + // here SomeRecord was created transiently, moved into the dropper-λ, + // used there as base for a derived struct DataLayer_something, which + // features an additional property `something`, initialised with an + // arbitrary value (and type) also passed on invocation. + // The result is a record object, which incorporates the base, together + // with an additional "Layer" holding the new property, stored efficiently. + + + using Compound = decltype(compoundRecord); +SHOW_TYPE(decltype(compoundRecord)) +SHOW_EXPR(TYPE(compoundRecord)) +SHOW_EXPR(sizeof(SomeRecord)) + CHECK (sizeof(SomeRecord) == sizeof(float)); +SHOW_EXPR(sizeof(Compound)) + CHECK (sizeof(Compound) >= sizeof(float)+sizeof(ushort)); +SHOW_EXPR(alignof(SomeRecord)) +SHOW_EXPR(alignof(Compound)) + CHECK (alignof(Compound) == alignof(SomeRecord)); + CHECK (sizeof(Compound) == 2 * alignof(float)); + +SHOW_EXPR(std::is_class_v) +SHOW_EXPR(std::is_trivial_v) +SHOW_EXPR(std::is_trivial_v) +SHOW_EXPR(std::is_trivially_copyable_v) +SHOW_EXPR(std::is_trivially_copyable_v) +SHOW_EXPR(std::is_standard_layout_v) + CHECK (std::is_class_v); + CHECK (std::is_trivial_v); + CHECK (std::is_trivial_v); + CHECK (std::is_trivially_copyable_v); + CHECK (std::is_trivially_copyable_v); + + // Yet obviously it is not a POD, + // since non-static members reside at several levels + CHECK (not std::is_standard_layout_v); + +SHOW_EXPR((is_Subclass_v)) + CHECK ((is_Subclass_v)); + } + + + /** @test show how to use generated records to implement a policy. + */ + void + demonstrate_usage_scenario() + { + } + }; + + + /** Register this test class... */ + LAUNCHER (PropBuilder_test, "unit meta"); + + + +}}} // namespace lib::meta::test diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index 1627b3111..431147082 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -58599,6 +58599,69 @@ + + + + + + + +

+ ...das will ich schon seit endlos langer Zeit, und bin aber immer wieder zu dem Schluß gekommen, daß es in C++ nicht wirklich geht, da uns die Introspektion von Record-Typen fehlt. +

+

+ 2026/2 : nun hatte ich wieder so einen Fall, und hab mich drei Tage lang grün und blau geärgert, weil es so sagenhaft umständlich (und auch verwirrend) ist, wenn man eine Policy definieren möchte, die mehrere Lambdas zur Konfiguration verwendet. Konkret war das (natürlich) nur ein Nebenschauplatz, und »eigentlich braucht man es nicht« ... +

+

+ jedenfalls hab ich mal wieder ganz locker ein Design gemacht, und mir gedacht „den Rest konfiguriert man dann ganz einfach per Policy“. Denkste. Und da ich jetzt schon so viel Zeit vergeudet habe (Hallo »sunken cost fallacy«, nice to meet you again!), hab ich mich auf eine Macro-basierte Lösung eingeschossen. Nachdem ich dann darüber zwei Tage gebrütet hatte, sind mir die λ-Template-Parameter eingefallen, die es ja erst seit C++20 gibt. Beim Nachlesen in der Doku bin ich dann zufällig auch noch auf die »Explicit object member functions« gestoßen — ein Feature, das mir komplett entgangen war. Beide zusammen sind ein echter Game-Changer! Denn damit kann man nun nicht nur einen Typ in einer Funktion generieren (das konnte man schon immer), sondern das per Lambda in einem Ausdruck machen, ohne noch eine separate Deklaration zu brauchen. Das erscheint mir jetzt als ein Quantensprung, und hebt das »Policy-based-Design« auf einen anderen Level der Expressivität. +

+ +
+ + +
+ + + + + +

+ ...das ist die entscheidende Verbesserung; abgesehen davon ist jede Klasse oder Struct ein »maßgeschneiderter Daten-Record« — aber die boilerplate  allein zum Initialisieren ist so umständlich, daß das Ergebnis nicht mehr selbsterklärend ist. +

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -114374,14 +114437,15 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) - - + + - + + - + @@ -114389,16 +114453,13 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) - - - +

das macht die sache nun extrem ekelhaft und verworren

- -
+
@@ -114411,9 +114472,7 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) - - - +

in der einfachsten Form muß der Aufrufer herumtricksen, @@ -114422,8 +114481,7 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) weil die Policy mit drei Lambdas konfiguriert wird...

- -
+
@@ -114435,29 +114493,23 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) - - - +

Das ist so vorgegeben durch das Design von BufferProviderSetup (weil ich damit die konkreten Backend-Klassen komplett aus dem Basis-Interface BufferProvider heraushalte)

- -
+
- - - +

BufferProviderSetup ruft aus dem Rumpf ihres Konstruktors zwei Setup-Methoden auf; das wars dann, danach wird die Setup-Klasse nicht mehr gebraucht

- -
+
@@ -114468,20 +114520,15 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) - - - +

Hier wird's dreist: Policy-Base per move aus der Setup-Hilfsklasse herausschieben !!!

- -
+ - - - +

Und zwar, noch dreister, während diese Setup-Klasse grade aufgerufen wird, um die Backends zu konfigurieren. Effektiv führt dieser Aufruf dazu, daß die Setup-Klasse ihre eigenen »Eingeweide« ausweidet und weitergibt. Kein Problem, sie stirbt ja sowiso am Ende des Aufrufts. @@ -114493,9 +114540,101 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) Noch genauer betrachtet: eine RValue-Referenz ist ja erst mal nur ein »Angebot«: sie macht etwas für move-Konstukrion (oder Zuweisung) zugänglich. Was konkret beim »move« passiert, legt der Konsument fest. Hier ist der Konsument aber tatsächlich nur an der Basis-Klasse interessiert, d.h. er konstruiert eine Kopie der Basisklasse per move-Konstruktur. Und wiederum, was dabei passiert, bestimmen die Member in dieser Policy. Erwartungsgemäß sind das Lambdas, und die könnten obtional tatsächlich etwas binden, was dann per Move-Konstruktion verschoben wird (z.B. einenen unique-PImpl). Sehr schön, denn Lambdas werden erst aktiv, wenn sie aufgerufen werden. Werden sie aber nicht, während der Konstruktion. Zusammengefaßt: solange nicht der Konstruktionsvorgang selbst die Callbacks aufruft, ist das alles wasserdicht. Die einzige (langfristige) Gefahr besteht darin, daß ein übereifriger Linter/Compiler in Zukunft hier einen use-after-delete markiert, ohne tatsächlich zu belegen, daß der use auftritt.

- -
+
+ + + + +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+ Das heißt: ich kann zwar eine Instanz einens anonymen Typs definieren  — aber das ist dann kein Ausdruck, und kann schon gleich garnicht ein Template sein +

+ +
+
+ + + + + + + + + + + + +

+ damit muß es gehen +

+ +
+ + + +

+ ich bekomme alle Typen in die Hand und kann damit eine neue Struct generieren +

+ +
+
+ + + +
+ + + + + + + + +
+ + + + + + + +