BufferProvider: research how to build a policy with callbacks
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.
This commit is contained in:
parent
77217d9785
commit
8437710232
14 changed files with 518 additions and 121 deletions
|
|
@ -1,56 +0,0 @@
|
|||
/*
|
||||
MAYBE-COMPARE.hpp - guarded invocation of comparisons
|
||||
|
||||
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 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<typename X>
|
||||
struct Comparator
|
||||
{
|
||||
static bool
|
||||
equals (X const& x1, X const& x2)
|
||||
{
|
||||
return x1 == x2;
|
||||
}
|
||||
};
|
||||
|
||||
/* == add specialisations here == */
|
||||
|
||||
|
||||
template<typename X>
|
||||
inline bool
|
||||
equals_safeInvoke (X const& x1, X const& x2)
|
||||
{
|
||||
return Comparator<X>::equals(x1,x2);
|
||||
}
|
||||
|
||||
|
||||
}} // namespace lib::meta
|
||||
#endif
|
||||
89
src/lib/meta/prop-builder.hpp
Normal file
89
src/lib/meta/prop-builder.hpp
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
/*
|
||||
PROP-BUILDER.hpp - generate data record with arbitrary named fields
|
||||
|
||||
Copyright (C)
|
||||
2026, 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 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 <utility>
|
||||
|
||||
|
||||
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<class BAS, class LAY, typename VAL>
|
||||
auto
|
||||
define (this BAS&& chain, LAY const& layerBuilder, VAL&& val)
|
||||
{
|
||||
return layerBuilder (move(chain), forward<VAL>(val));
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
#define PROP_FIELD(FIELD_NAME) \
|
||||
[]<class BAS, typename VAL> \
|
||||
(BAS&& bas, VAL&& val) \
|
||||
{ \
|
||||
using BaseLayer = std::decay_t<BAS>;\
|
||||
using Property = std::decay_t<VAL>; \
|
||||
\
|
||||
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>(bas)\
|
||||
,forward<VAL>(val) \
|
||||
}; \
|
||||
}
|
||||
|
||||
|
||||
|
||||
}} // namespace lib::meta
|
||||
#endif /*LIB_META_PROP_BUILDER_H*/
|
||||
|
|
@ -57,9 +57,6 @@
|
|||
|
||||
|
||||
//Forward declarations for the Unwrap helper....
|
||||
namespace boost{
|
||||
template<class X> class reference_wrapper;
|
||||
}
|
||||
namespace std {
|
||||
template<class X> class reference_wrapper;
|
||||
template<class X> class shared_ptr;
|
||||
|
|
@ -159,19 +156,6 @@ namespace meta {
|
|||
}
|
||||
};
|
||||
|
||||
template<typename X>
|
||||
struct Unwrap<boost::reference_wrapper<X>>
|
||||
: std::true_type
|
||||
{
|
||||
using Type = X;
|
||||
|
||||
static X&
|
||||
extract (boost::reference_wrapper<X> wrapped)
|
||||
{
|
||||
return wrapped;
|
||||
}
|
||||
};
|
||||
|
||||
template<typename X>
|
||||
struct Unwrap<std::reference_wrapper<X>>
|
||||
: std::true_type
|
||||
|
|
@ -328,7 +312,11 @@ namespace meta {
|
|||
, is_same<I,S>
|
||||
>
|
||||
{ };
|
||||
|
||||
|
||||
template<typename S, typename I>
|
||||
inline constexpr bool is_Subclass_v = is_Subclass<S,I>();
|
||||
|
||||
|
||||
/** compare for unadorned base type, disregarding const and references */
|
||||
template<typename S, typename I>
|
||||
struct is_basically
|
||||
|
|
|
|||
|
|
@ -130,7 +130,7 @@ namespace lib {
|
|||
TY&
|
||||
create (ARGS&& ...args)
|
||||
{
|
||||
static_assert ( meta::is_Subclass<TY,I>()
|
||||
static_assert ( meta::is_Subclass_v<TY,I>
|
||||
&& sizeof(TY) <= siz,
|
||||
"ElementHolder buffer too small");
|
||||
|
||||
|
|
|
|||
|
|
@ -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<TVal,IVal>() and has_virtual_destructor_v<IVal>)
|
||||
if (is_Subclass_v<TVal,IVal> and has_virtual_destructor_v<IVal>)
|
||||
{
|
||||
__ensureMark<TVal> (VIRTUAL);
|
||||
return [factory](ArrayBucket<I>* bucket){ unConst(factory).template destroy<IVal> (bucket); };
|
||||
|
|
@ -753,7 +753,7 @@ namespace lib {
|
|||
__ensureMark<TVal> (TRIVIAL);
|
||||
return [factory](ArrayBucket<I>* bucket){ unConst(factory).template destroy<TVal> (bucket); };
|
||||
}
|
||||
if (is_same_v<TVal,EVal> and is_Subclass<EVal,IVal>())
|
||||
if (is_same_v<TVal,EVal> and is_Subclass_v<EVal,IVal>)
|
||||
{
|
||||
__ensureMark<TVal> (ELEMENT);
|
||||
return [factory](ArrayBucket<I>* bucket){ unConst(factory).template destroy<EVal> (bucket); };
|
||||
|
|
|
|||
|
|
@ -741,7 +741,7 @@ namespace lib {
|
|||
inline void
|
||||
launchDetached (ThreadHookable::Launch&& launchBuilder)
|
||||
{
|
||||
static_assert (lib::meta::is_Subclass<TAR, ThreadHookable>());
|
||||
static_assert (lib::meta::is_Subclass_v<TAR, ThreadHookable>);
|
||||
|
||||
new TAR{move(launchBuilder)
|
||||
.atExit([](TAR& selfAllocation)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@ namespace model {
|
|||
class ZoomMetric
|
||||
: public I
|
||||
{
|
||||
static_assert(lib::meta::is_Subclass<I, DisplayMetric>());
|
||||
static_assert(lib::meta::is_Subclass_v<I, DisplayMetric>);
|
||||
|
||||
protected:
|
||||
ZoomWindow zoomWindow_;
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -296,6 +296,11 @@ return: 0
|
|||
END
|
||||
|
||||
|
||||
TEST "Generated data records" PropBuilder_test <<END
|
||||
return: 0
|
||||
END
|
||||
|
||||
|
||||
TEST "Variadic template diagnostics" TestHelperVariadic_test <<END
|
||||
out-lit: --no-arg--
|
||||
out-lit: :.
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ namespace lib {
|
|||
namespace test{
|
||||
|
||||
using std::string;
|
||||
using meta::is_Subclass;
|
||||
using meta::is_Subclass_v;
|
||||
using util::isSameObject;
|
||||
using util::isSameAdr;
|
||||
using util::getAdr;
|
||||
|
|
@ -80,7 +80,7 @@ namespace test{
|
|||
verify_FrontBlock()
|
||||
{
|
||||
using Block1 = HeteroData<uint,double>;
|
||||
CHECK ((is_Subclass<Block1::NewFrame, std::tuple<uint,double>>()));
|
||||
CHECK ((is_Subclass_v<Block1::NewFrame, std::tuple<uint,double>>));
|
||||
|
||||
auto b1 = Block1::build (42, 1.61803);
|
||||
CHECK (1.61803 == b1.get<1>());
|
||||
|
|
@ -109,11 +109,11 @@ namespace test{
|
|||
verify_ChainBlock()
|
||||
{
|
||||
using Block1 = HeteroData<uint>;
|
||||
CHECK ((is_Subclass<Block1::NewFrame, std::tuple<uint>>()));
|
||||
CHECK ((is_Subclass_v<Block1::NewFrame, std::tuple<uint>>));
|
||||
|
||||
using Constructor = Block1::Chain<double,string>;
|
||||
using Block2 = Constructor::NewFrame;
|
||||
CHECK ((is_Subclass<Block2, std::tuple<double, string>>()));
|
||||
CHECK ((is_Subclass_v<Block2, std::tuple<double, string>>));
|
||||
|
||||
auto b1 = Block1::build (41);
|
||||
auto b2 = Constructor::build (1.61, "Φ");
|
||||
|
|
|
|||
235
tests/library/meta/prop-builder-test.cpp
Normal file
235
tests/library/meta/prop-builder-test.cpp
Normal file
|
|
@ -0,0 +1,235 @@
|
|||
/*
|
||||
PropBuilder(Test) - verify helpers for working with tuples and type sequences
|
||||
|
||||
Copyright (C)
|
||||
2026, 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 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 <string>
|
||||
|
||||
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<decltype(_TY_)>()
|
||||
|
||||
|
||||
|
||||
/*********************************************************************//**
|
||||
* @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<class X>
|
||||
string
|
||||
showSelfType (this X&)
|
||||
{
|
||||
return showType<X>();
|
||||
}
|
||||
};
|
||||
|
||||
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 = []<class BAS, typename VAL>(BAS&& bas, VAL&& val)
|
||||
{
|
||||
using BaseLayer = std::decay_t<BAS>;
|
||||
using Property = std::decay_t<VAL>;
|
||||
|
||||
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>(bas)
|
||||
,forward<VAL>(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<Compound>)
|
||||
SHOW_EXPR(std::is_trivial_v<Compound>)
|
||||
SHOW_EXPR(std::is_trivial_v<SomeRecord>)
|
||||
SHOW_EXPR(std::is_trivially_copyable_v<SomeRecord>)
|
||||
SHOW_EXPR(std::is_trivially_copyable_v<Compound>)
|
||||
SHOW_EXPR(std::is_standard_layout_v<Compound>)
|
||||
CHECK (std::is_class_v<Compound>);
|
||||
CHECK (std::is_trivial_v<Compound>);
|
||||
CHECK (std::is_trivial_v<SomeRecord>);
|
||||
CHECK (std::is_trivially_copyable_v<Compound>);
|
||||
CHECK (std::is_trivially_copyable_v<SomeRecord>);
|
||||
|
||||
// Yet obviously it is not a POD,
|
||||
// since non-static members reside at several levels
|
||||
CHECK (not std::is_standard_layout_v<Compound>);
|
||||
|
||||
SHOW_EXPR((is_Subclass_v<Compound, SomeRecord>))
|
||||
CHECK ((is_Subclass_v<Compound, SomeRecord>));
|
||||
}
|
||||
|
||||
|
||||
/** @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
|
||||
|
|
@ -58599,6 +58599,69 @@
|
|||
<node CREATED="1751414247721" ID="ID_1171949349" MODIFIED="1751414277013" TEXT="⟹ lib::meta::apply als Ersatz (und lib::meta::getElm)"/>
|
||||
</node>
|
||||
</node>
|
||||
<node CREATED="1772066411633" ID="ID_50275437" MODIFIED="1772066416949" TEXT="Code-Generierung">
|
||||
<node BACKGROUND_COLOR="#fdfdcf" COLOR="#ff0000" CREATED="1772066190351" ID="ID_940571255" MODIFIED="1772066239070" TEXT="PropBuilder : Record mit benannten Feldern generieren">
|
||||
<icon BUILTIN="yes"/>
|
||||
<node CREATED="1772066253767" ID="ID_1072763198" MODIFIED="1772067242722" TEXT="Neue Metaprgramming-Technik ⟸ für Policy-based Design">
|
||||
<richcontent TYPE="NOTE"><html>
|
||||
<head/>
|
||||
<body>
|
||||
<p>
|
||||
...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.
|
||||
</p>
|
||||
<p>
|
||||
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 <i>mehrere Lambdas</i> zur Konfiguration verwendet. Konkret war das (natürlich) nur ein Nebenschauplatz, und »eigentlich braucht man es nicht« ...
|
||||
</p>
|
||||
<p>
|
||||
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 <i>sind ein echter Game-Changer!</i> Denn damit kann man nun nicht nur einen Typ in einer Funktion generieren (das konnte man schon immer), sondern das per Lambda <b>in einem Ausdruck</b> 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.
|
||||
</p>
|
||||
</body>
|
||||
</html></richcontent>
|
||||
<linktarget COLOR="#2b57f9" DESTINATION="ID_1072763198" ENDARROW="Default" ENDINCLINATION="2965;140;" ID="Arrow_ID_198810341" SOURCE="ID_1029922863" STARTARROW="None" STARTINCLINATION="-2090;136;"/>
|
||||
<icon BUILTIN="info"/>
|
||||
</node>
|
||||
<node CREATED="1772067250896" ID="ID_1640694078" MODIFIED="1772067306544" TEXT="Aufgabe: einen »maßgeschneiderten« Daten-Record...">
|
||||
<node CREATED="1772067307899" ID="ID_919196456" MODIFIED="1772067410868" TEXT="kompakt und lesbar »inline« definieren">
|
||||
<richcontent TYPE="NOTE"><html>
|
||||
<head/>
|
||||
<body>
|
||||
<p>
|
||||
...das ist die entscheidende Verbesserung; abgesehen davon ist jede Klasse oder Struct ein »maßgeschneiderter Daten-Record« — aber die <i>boilerplate</i>  allein zum Initialisieren ist so umständlich, daß das Ergebnis nicht mehr <i>selbsterklärend ist.</i>
|
||||
</p>
|
||||
</body>
|
||||
</html></richcontent>
|
||||
</node>
|
||||
<node CREATED="1772067418044" ID="ID_1314882599" MODIFIED="1772067438651" TEXT="und gleich mit inline-λ und Kontext-capture initialisieren"/>
|
||||
</node>
|
||||
<node CREATED="1772068221512" ID="ID_774607578" MODIFIED="1772070444439" TEXT="Bau-Technik">
|
||||
<icon BUILTIN="forward"/>
|
||||
<node COLOR="#435e98" CREATED="1772068227431" ID="ID_361097452" MODIFIED="1772070455630" TEXT="ein Builder-Ausdruck"/>
|
||||
<node COLOR="#435e98" CREATED="1772068233846" ID="ID_1873632379" MODIFIED="1772070455630" TEXT="ein Lambda, das einen abgeleiteten Typ definiert">
|
||||
<node CREATED="1772068272529" ID="ID_57203075" MODIFIED="1772068277732" TEXT="es nimmt ein Basis-Objekt"/>
|
||||
<node CREATED="1772068278549" ID="ID_1359999705" MODIFIED="1772068285633" TEXT="plus einen Property-Wert"/>
|
||||
<node CREATED="1772068286375" ID="ID_1783360048" MODIFIED="1772068300481" TEXT="und erzeugt eine abgeleitete Klasse mit diesem Property"/>
|
||||
</node>
|
||||
<node COLOR="#435e98" CREATED="1772068303510" ID="ID_142658280" MODIFIED="1772070460038" TEXT="Definition und Aufruf dieses Lambdas kann man in einen einzigen Ausdruck packen">
|
||||
<icon BUILTIN="idea"/>
|
||||
</node>
|
||||
<node COLOR="#435e98" CREATED="1772068327705" ID="ID_782344387" MODIFIED="1772070455629" TEXT="und diesen Ausdruck kann man dann mit einem Macro generieren">
|
||||
<node CREATED="1772068339947" ID="ID_721460446" MODIFIED="1772068350577" TEXT="damit werden neue Feldnamen möglich"/>
|
||||
<node CREATED="1772068351334" ID="ID_302440640" MODIFIED="1772068360385" TEXT="und man kann auch den generierten Typ dekorieren"/>
|
||||
</node>
|
||||
</node>
|
||||
<node CREATED="1772068366233" ID="ID_858655013" MODIFIED="1772068376142" TEXT="Implementierung">
|
||||
<node CREATED="1772068377138" ID="ID_160543739" MODIFIED="1772068386687" TEXT="lib/meta/prop-builder.hpp"/>
|
||||
<node BACKGROUND_COLOR="#eef0c5" COLOR="#990000" CREATED="1772068387673" ID="ID_999468145" MODIFIED="1772068449713" TEXT="PropBuilder_test">
|
||||
<icon BUILTIN="pencil"/>
|
||||
<node CREATED="1772068394858" ID="ID_1256049272" MODIFIED="1772068407518" TEXT="Aufruf-Syntax zeigen"/>
|
||||
<node BACKGROUND_COLOR="#eef0c5" COLOR="#990000" CREATED="1772068408103" ID="ID_452419709" MODIFIED="1772068442634" TEXT="die verwendeten Programmiertechniken dokumentieren">
|
||||
<icon BUILTIN="pencil"/>
|
||||
</node>
|
||||
<node CREATED="1772068421596" ID="ID_1602327531" MODIFIED="1772068435958" TEXT="Beispiel für ein Policy-based Design darststellen"/>
|
||||
</node>
|
||||
</node>
|
||||
</node>
|
||||
</node>
|
||||
<node CREATED="1540682664865" ID="ID_1702211999" MODIFIED="1557498707236" TEXT="Metaprogramming">
|
||||
<node CREATED="1739742552923" ID="ID_606595417" MODIFIED="1739742563204" TEXT="Typsequenzen und Typlisten">
|
||||
<node BACKGROUND_COLOR="#ccb59b" COLOR="#6e2a38" CREATED="1739742570360" ID="ID_1552300649" MODIFIED="1739743026716" TEXT="Grundsätzlich: die Loki-Typlisten / Sequenzen werde ich weiterhin nutzen und erhalten">
|
||||
|
|
@ -114374,14 +114437,15 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
|
|||
<node CREATED="1771634200296" ID="ID_1781890966" MODIFIED="1771634221593" TEXT="das ist angemessen; es geht um Implementierungs-Technik"/>
|
||||
<node CREATED="1771634718849" ID="ID_158495787" MODIFIED="1771634743108" TEXT="man könnte zudem die Anforderungen als Concept dokumentieren"/>
|
||||
</node>
|
||||
<node BACKGROUND_COLOR="#eef0c5" COLOR="#990000" CREATED="1771807890857" ID="ID_44174155" MODIFIED="1771809182613" TEXT="Experiment zur Policy">
|
||||
<icon BUILTIN="pencil"/>
|
||||
<node COLOR="#338800" CREATED="1771807890857" ID="ID_44174155" MODIFIED="1771893611677" TEXT="Experiment zur Policy">
|
||||
<icon BUILTIN="button_ok"/>
|
||||
<node CREATED="1771807898504" ID="ID_1896427982" MODIFIED="1771807909962" TEXT="einfachste Form: eine Policy als Baseclass"/>
|
||||
<node CREATED="1771807911166" ID="ID_1166005757" MODIFIED="1771807924271" TEXT="die Policy selber soll moveable sein"/>
|
||||
<node BACKGROUND_COLOR="#f8f1cb" COLOR="#a50125" CREATED="1771807934449" ID="ID_292154472" MODIFIED="1771807951937" TEXT="Problem: ich brauch die Policy in der Store-Implementierung">
|
||||
<node BACKGROUND_COLOR="#c8c0b6" COLOR="#435e98" CREATED="1771807934449" ID="ID_292154472" MODIFIED="1771893713330" TEXT="Problem: ich brauch die Policy in der Store-Implementierung">
|
||||
<arrowlink COLOR="#5ba4a3" DESTINATION="ID_937919882" ENDARROW="Default" ENDINCLINATION="-525;70;" ID="Arrow_ID_487502478" STARTARROW="None" STARTINCLINATION="111;5;"/>
|
||||
<icon BUILTIN="messagebox_warning"/>
|
||||
</node>
|
||||
<node BACKGROUND_COLOR="#fafe99" COLOR="#fa002a" CREATED="1771808870215" ID="ID_1799682600" MODIFIED="1771808894898" TEXT="damit verbundenes Problem: Konfiguration des BufferProviderSetup">
|
||||
<node BACKGROUND_COLOR="#d7b089" COLOR="#9c0648" CREATED="1771808870215" ID="ID_1799682600" MODIFIED="1771893643654" TEXT="damit verbundenes Problem: Konfiguration des BufferProviderSetup">
|
||||
<icon BUILTIN="broken-line"/>
|
||||
<node CREATED="1771808897410" ID="ID_1165872773" MODIFIED="1771808913796" TEXT="den müssen wir ebenfalls per Policy konfigurieren">
|
||||
<node CREATED="1771808914975" ID="ID_1601123928" MODIFIED="1771808929187" TEXT="damit wir die Spezialisierung erst im Konstruktor-Rumpf brauchen"/>
|
||||
|
|
@ -114389,16 +114453,13 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
|
|||
</node>
|
||||
<node CREATED="1771808957102" ID="ID_266948865" MODIFIED="1771808988313">
|
||||
<richcontent TYPE="NODE"><html>
|
||||
<head>
|
||||
|
||||
</head>
|
||||
<head/>
|
||||
<body>
|
||||
<p>
|
||||
das macht die sache nun <font size="4" color="#aa0707">extrem ekelhaft und verworren</font>
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
</richcontent>
|
||||
</html></richcontent>
|
||||
<icon BUILTIN="smiley-angry"/>
|
||||
</node>
|
||||
<node BACKGROUND_COLOR="#c8c0b6" COLOR="#435e98" CREATED="1771808997763" ID="ID_251025614" MODIFIED="1771809261721" TEXT="irgendwie durchprügeln">
|
||||
|
|
@ -114411,9 +114472,7 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
|
|||
<node CREATED="1771809101214" ID="ID_398957418" MODIFIED="1771809112001" TEXT="die Store-Implementierung hat jetzt eine Back-Referenz"/>
|
||||
<node CREATED="1771809113111" ID="ID_300180321" MODIFIED="1771809161169">
|
||||
<richcontent TYPE="NODE"><html>
|
||||
<head>
|
||||
|
||||
</head>
|
||||
<head/>
|
||||
<body>
|
||||
<p>
|
||||
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 <b>drei Lambdas<i> </i></b>konfiguriert wird...
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
</richcontent>
|
||||
</html></richcontent>
|
||||
<icon BUILTIN="smily_bad"/>
|
||||
</node>
|
||||
</node>
|
||||
|
|
@ -114435,29 +114493,23 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
|
|||
<icon BUILTIN="idea"/>
|
||||
<node CREATED="1771857680890" ID="ID_1386032779" MODIFIED="1771857785144" TEXT="wir brauchen eine Setup-Hilfsklasse, um die Backend-Implementierungen zu erzeugen">
|
||||
<richcontent TYPE="NOTE"><html>
|
||||
<head>
|
||||
|
||||
</head>
|
||||
<head/>
|
||||
<body>
|
||||
<p>
|
||||
Das ist so vorgegeben durch das Design von BufferProviderSetup (weil ich damit die konkreten Backend-Klassen komplett aus dem Basis-Interface BufferProvider heraushalte)
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
</richcontent>
|
||||
</html></richcontent>
|
||||
</node>
|
||||
<node CREATED="1771857787371" ID="ID_1223489355" MODIFIED="1771857849302" TEXT="diese brauchen tatsächlich nur während der Dauer des Konstruktur-Aufrufs">
|
||||
<richcontent TYPE="NOTE"><html>
|
||||
<head>
|
||||
|
||||
</head>
|
||||
<head/>
|
||||
<body>
|
||||
<p>
|
||||
BufferProviderSetup ruft aus dem Rumpf ihres Konstruktors zwei Setup-Methoden auf; das wars dann, danach wird die Setup-Klasse nicht mehr gebraucht
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
</richcontent>
|
||||
</html></richcontent>
|
||||
</node>
|
||||
<node CREATED="1771857855938" ID="ID_468915370" MODIFIED="1771857872747" TEXT="diese Setup-Klasse kann ein Dekorator über die Policy sein">
|
||||
<icon BUILTIN="idea"/>
|
||||
|
|
@ -114468,20 +114520,15 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
|
|||
<node CREATED="1771857921329" ID="ID_233147109" MODIFIED="1771857966470" TEXT="⟹ also kann die Policy letztlich dorthin geschoben werden, als Mix-in (private Basisklasse)"/>
|
||||
<node CREATED="1771857999246" ID="ID_142868114" MODIFIED="1771858675941">
|
||||
<richcontent TYPE="NODE"><html>
|
||||
<head>
|
||||
|
||||
</head>
|
||||
<head/>
|
||||
<body>
|
||||
<p>
|
||||
<b><u>Hier wird's dreist</u></b>: Policy-Base per move aus der Setup-Hilfsklasse herausschieben !!!
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
</richcontent>
|
||||
</html></richcontent>
|
||||
<richcontent TYPE="NOTE"><html>
|
||||
<head>
|
||||
|
||||
</head>
|
||||
<head/>
|
||||
<body>
|
||||
<p>
|
||||
Und zwar, noch dreister, <i>während</i> 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. <i><font color="#770d2c">Kein Problem, sie stirbt ja sowiso am Ende des Aufrufts. </font></i>
|
||||
|
|
@ -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. <i>Was konkret beim »move« passiert</i>, 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: <i>solange nicht der Konstruktionsvorgang selbst die Callbacks aufruft,</i> 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.
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
</richcontent>
|
||||
</html></richcontent>
|
||||
</node>
|
||||
<node COLOR="#338800" CREATED="1771858723755" ID="ID_937919882" MODIFIED="1771893682325" TEXT="Test: funktioniert">
|
||||
<linktarget COLOR="#5ba4a3" DESTINATION="ID_937919882" ENDARROW="Default" ENDINCLINATION="-525;70;" ID="Arrow_ID_487502478" SOURCE="ID_292154472" STARTARROW="None" STARTINCLINATION="111;5;"/>
|
||||
<icon BUILTIN="button_ok"/>
|
||||
</node>
|
||||
</node>
|
||||
</node>
|
||||
</node>
|
||||
<node BACKGROUND_COLOR="#eef0c5" COLOR="#990000" CREATED="1771893568172" ID="ID_465309080" MODIFIED="1771893720003" TEXT="Zweiter Teil der Aufgabe: named-Lambda-Tupel ">
|
||||
<icon BUILTIN="pencil"/>
|
||||
<node BACKGROUND_COLOR="#e0ceaa" COLOR="#690f14" CREATED="1771893733865" ID="ID_60984394" MODIFIED="1771893764443" TEXT="ich möchte ALLES">
|
||||
<icon BUILTIN="yes"/>
|
||||
<node CREATED="1771893742904" ID="ID_921712765" MODIFIED="1771893750874" TEXT="einen meta-konfigurierbaren Builder"/>
|
||||
<node CREATED="1771893769036" ID="ID_434009909" MODIFIED="1771893878715" TEXT="einzelne Layer mit einem λ-Attribut von generischem Typ ⟹ MACRO"/>
|
||||
<node CREATED="1771893845362" ID="ID_112946042" MODIFIED="1771893872082" TEXT="UND das λ soll aber noch lokal gegeben werden können ⟹ Kontext-Binding"/>
|
||||
</node>
|
||||
<node BACKGROUND_COLOR="#e0ceaa" COLOR="#690f14" CREATED="1771893896510" ID="ID_771196335" MODIFIED="1771895013673" TEXT="Ideen sammeln">
|
||||
<icon BUILTIN="idea"/>
|
||||
<node BACKGROUND_COLOR="#f8f1cb" COLOR="#a50125" CREATED="1771893905186" ID="ID_1241420221" MODIFIED="1771893946302" TEXT="nicht klar ob es geht .... die Teile passen noch nicht recht zusammen (in meinem Kopf)">
|
||||
<icon BUILTIN="messagebox_warning"/>
|
||||
</node>
|
||||
<node CREATED="1771893948001" ID="ID_898834553" MODIFIED="1771894995111" TEXT="einen Builder, der per Macro generierte nested-Classes zusammenhängt">
|
||||
<icon BUILTIN="full-1"/>
|
||||
</node>
|
||||
<node CREATED="1771893977920" ID="ID_327025336" MODIFIED="1771894999397" TEXT="ein Tuple mit den Lambdas, und einen named accessor-Layer darüber (per Macro)">
|
||||
<icon BUILTIN="full-2"/>
|
||||
</node>
|
||||
<node CREATED="1771894975460" ID="ID_1705020209" MODIFIED="1771895021404" TEXT="möglicherweise zusammenbinden per Typ-Sequenz-Applikation">
|
||||
<icon BUILTIN="full-3"/>
|
||||
</node>
|
||||
</node>
|
||||
<node BACKGROUND_COLOR="#fafe99" COLOR="#fa002a" CREATED="1772064586741" ID="ID_1333957843" MODIFIED="1772064712945" TEXT="das Problem ist vor allem syntaktischer Natur">
|
||||
<icon BUILTIN="broken-line"/>
|
||||
<node CREATED="1772064634844" ID="ID_1613330952" MODIFIED="1772064634844" TEXT="Typen müssen deklariert werden"/>
|
||||
<node CREATED="1772064649488" ID="ID_806205089" MODIFIED="1772064649488" TEXT="Instanzen müssen definiert werden"/>
|
||||
<node CREATED="1772064692061" ID="ID_1021370131" MODIFIED="1772064692061" TEXT="Ich will aber nur EIN Macro pro »Layer«">
|
||||
<icon BUILTIN="yes"/>
|
||||
<node CREATED="1772064787000" ID="ID_1407594054" MODIFIED="1772064921190" TEXT="eigentlich kann man in C++ keine Typen „inline“ (in einem Ausdruck) deklarieren">
|
||||
<richcontent TYPE="NOTE"><html>
|
||||
<head/>
|
||||
<body>
|
||||
<p>
|
||||
Das heißt: ich kann zwar eine Instanz einens anonymen Typs <i>definieren</i>  — aber das ist dann kein Ausdruck, und kann schon gleich garnicht ein Template sein
|
||||
</p>
|
||||
</body>
|
||||
</html></richcontent>
|
||||
</node>
|
||||
<node CREATED="1772064936748" ID="ID_1768539918" MODIFIED="1772064936748" TEXT="aber ich kann einen lokalen Typ aus einer Funktion heraus »abwerfen«">
|
||||
<icon BUILTIN="idea"/>
|
||||
</node>
|
||||
<node CREATED="1772064976990" ID="ID_1522870120" MODIFIED="1772064976990" TEXT="also einen Spalt breit ist die Tür offen — aber ich bekomme meinen Fuß nicht rein...">
|
||||
<icon BUILTIN="smiley-oh"/>
|
||||
</node>
|
||||
<node CREATED="1772065106974" ID="ID_9798987" MODIFIED="1772065141875" TEXT="Ha! C++20 ⟹ Lambdas können Template-Parameter haben">
|
||||
<icon BUILTIN="forward"/>
|
||||
<node CREATED="1772065144931" ID="ID_1572938824" MODIFIED="1772065185213">
|
||||
<richcontent TYPE="NODE"><html>
|
||||
<head/>
|
||||
<body>
|
||||
<p>
|
||||
damit <b>muß</b> es gehen
|
||||
</p>
|
||||
</body>
|
||||
</html></richcontent>
|
||||
<richcontent TYPE="NOTE"><html>
|
||||
<head/>
|
||||
<body>
|
||||
<p>
|
||||
ich bekomme alle Typen in die Hand und kann damit eine neue Struct generieren
|
||||
</p>
|
||||
</body>
|
||||
</html></richcontent>
|
||||
</node>
|
||||
<node COLOR="#bf045e" CREATED="1772065089849" ID="ID_543012886" MODIFIED="1772065208074" TEXT="bekomme es aber seit gestern nicht in den Griff">
|
||||
<font ITALIC="true" NAME="SansSerif" SIZE="12"/>
|
||||
</node>
|
||||
</node>
|
||||
<node CREATED="1772065296622" ID="ID_1991045470" MODIFIED="1772065345149" TEXT="Ha² : kann in C++20 den »self«-Type in einer Member-Funktion abgreifen">
|
||||
<icon BUILTIN="forward"/>
|
||||
</node>
|
||||
<node CREATED="1772065459825" ID="ID_1413726983" MODIFIED="1772065483778" TEXT="⟹ damit kann ich alles in einen einzigen Member-Funktions-Aufruf packen"/>
|
||||
</node>
|
||||
<node CREATED="1772065364753" ID="ID_1128216728" MODIFIED="1772065409930" TEXT="Das waren jetzt zwei Tage Knobelei">
|
||||
<icon BUILTIN="smiley-neutral"/>
|
||||
</node>
|
||||
</node>
|
||||
<node BACKGROUND_COLOR="#eef0c5" COLOR="#990000" CREATED="1772065498436" ID="ID_1029922863" MODIFIED="1772066452619" TEXT="Neues generisches Feature: PropBuilder">
|
||||
<arrowlink COLOR="#2b57f9" DESTINATION="ID_1072763198" ENDARROW="Default" ENDINCLINATION="2965;140;" ID="Arrow_ID_198810341" STARTARROW="None" STARTINCLINATION="-2090;136;"/>
|
||||
<icon BUILTIN="pencil"/>
|
||||
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1772066464826" ID="ID_534123438" MODIFIED="1772066473089" TEXT="dokumentiert in PropBuilder_test">
|
||||
<icon BUILTIN="flag-yellow"/>
|
||||
</node>
|
||||
<node BACKGROUND_COLOR="#fdfdcf" COLOR="#ff0000" CREATED="1772066474417" ID="ID_1404760011" MODIFIED="1772066498094" TEXT="dort auch direkt diesen use-Case demonstrieren">
|
||||
<icon BUILTIN="yes"/>
|
||||
</node>
|
||||
</node>
|
||||
</node>
|
||||
|
|
|
|||
Loading…
Reference in a new issue