From d3270946036e8ea3f54ee971628371b475c4c235 Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Sat, 15 Jun 2024 01:14:42 +0200 Subject: [PATCH] Library: draft a scheme to configure `lib::Several` with a custom allocator MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Phew... this was a tough one — and not sure yet if this even remotely works... Anyway, the `lib::SeveralBuilder` is already prepared for collaboration with a custom allocator, since it delegates all memory handling through a base policy, which in turn relies on std::allocator_traits. The challenge however is to find a way... * to make this clear and easy to use * to expose an extension point for specific tweaks * and to make all this work without excessive header cross dependencies --- src/lib/allocation-cluster.hpp | 54 +++- src/lib/several-builder.hpp | 96 ++++++- src/lib/test/test-helper.hpp | 59 ++-- tests/12metaprogramming.tests | 23 +- .../test/test-helper-variadic-test.cpp | 17 +- wiki/thinkPad.ichthyo.mm | 270 +++++++++++++++++- 6 files changed, 458 insertions(+), 61 deletions(-) diff --git a/src/lib/allocation-cluster.hpp b/src/lib/allocation-cluster.hpp index df44b620f..f3a78f3ea 100644 --- a/src/lib/allocation-cluster.hpp +++ b/src/lib/allocation-cluster.hpp @@ -37,6 +37,11 @@ ** including the invocation of their destructors, while relying on the allocator ** to allot and discard bare memory. However, to avoid invoking any destructors, ** the container itself can be created with AllocationCluster::createDisposable. + ** \par dynamic adjustments + ** Under controlled conditions, it is possible to change the size of the latest + ** raw allocation handed out, within the limits of the available reserve in the + ** current memory extent. Obviously, this is a dangerous low-level feature, yet + ** offers some flexibility for containers and allocation schemes built on top. ** @warning deliberately *not threadsafe*. ** @remark confine usage to a single thread or use thread-local clusters. ** @see allocation-cluster-test.cpp @@ -269,7 +274,7 @@ namespace lib { AllocationCluster::Storage::adjustPos (int offset) ///< @warning be sure a negative offset is properly limited { REQUIRE (pos); - REQUIRE (hasReserve (rest)); + REQUIRE (hasReserve (offset)); pos = bytePos() + offset; rest -= offset; } @@ -287,5 +292,52 @@ namespace lib { } + + + + //-----Policies-to-use-AllocationCluster------------------------ + + namespace { + // Forward declaration: configuration policy for lib::SeveralBuilder + template class ALO> + struct AllocationPolicy; + } + + namespace allo { // Setup for custom allocator policies + + template class ALO, typename...ARGS> + struct SetupSeveral; + + /** + * Specialisation to use lib::Several with storage managed by an + * AllocationCluster instance, which must be provided as argument. + * \code + * AllocationCluster clu; + * using Data = .... + * Several elms = makeSeveral() + * .withAllocator(clu) + * .fillElm(5) + * .build(); + * \endcode + */ + template<> + struct SetupSeveral + { + template + using Adapter = typename AllocationCluster::template Allocator; + + template + struct Policy + : AllocationPolicy + { + Policy (AllocationCluster& clu) + : AllocationPolicy (clu.getAllocator()) + { } + }; + }; + // + }//(End)Allocator configuration + + } // namespace lib #endif /*LIB_ALLOCATION_CLUSTER_H*/ diff --git a/src/lib/several-builder.hpp b/src/lib/several-builder.hpp index 8077da5d3..31f73e544 100644 --- a/src/lib/several-builder.hpp +++ b/src/lib/several-builder.hpp @@ -336,6 +336,13 @@ namespace lib { /* ===== Builder API ===== */ + /** cross-builder to use a custom allocator for the lib::Several container */ + template class ALO =std::void_t + ,typename...ARGS> + auto withAllocator (ARGS&& ...args); + + + /** ensure sufficient memory allocation up-front */ template SeveralBuilder&& reserve (size_t cntElm =1 @@ -348,6 +355,7 @@ namespace lib { return move(*this); } + /** append copies of one or several arbitrary elements */ template SeveralBuilder&& append (VAL&& val, VALS&& ...vals) @@ -359,6 +367,7 @@ namespace lib { return move(*this); } + /** append a copy of all values exposed through an iterator */ template SeveralBuilder&& appendAll (IT&& data) @@ -377,6 +386,7 @@ namespace lib { return move(*this); } + /** emplace a number of elements of the defined element type \a E */ template SeveralBuilder&& fillElm (size_t cntNew, ARGS&& ...args) @@ -386,6 +396,7 @@ namespace lib { return move(*this); } + /** create a new content element within the managed storage */ template SeveralBuilder&& emplace (ARGS&& ...args) @@ -656,6 +667,84 @@ namespace lib { + /* ===== Helpers and convenience-functions for creating SeveralBuilder ===== */ + + + namespace allo { // Setup for custom allocator policies + + template class ALO, typename...ARGS> + struct SetupSeveral; + + template class ALO> + struct SetupSeveral + { + template + using Policy = AllocationPolicy; + }; + + template class ALO, typename X> + struct SetupSeveral> + { + template + struct Policy + : AllocationPolicy + { + Policy (ALO refAllocator) + : AllocationPolicy(move(refAllocator)) + { } + }; + }; + // + }//(End)Allocator configuration + + + + /** + * @remarks this builder notation configures the new lib::Several container + * to perform memory management through a standard conformant allocation adapter. + * Moreover, optionally the behaviour can be configured through an extension point + * lib::allo::SetupSeveral, for which the custom allocator may provide an explicit + * template specialisation. + * @tparam ALO a C++ standard conformant allocator template, which can be instantiated + * for creating various data elements. Notably, this will be instantiated as + * `ALO` to create and destroy the memory buffer for content data + * @param args optional dependency wiring arguments, to be passed to the allocator + * @return a new empty SeveralBuilder, configured to use the custom allocator. + */ + template + template class ALO, typename...ARGS> + inline auto + SeveralBuilder::withAllocator (ARGS&& ...args) + { + if (not empty()) + throw err::Logic{"lib::Several builder withAllocator() must be invoked " + "prior to adding any elements to the container"}; + + using Setup = allo::SetupSeveral; + using PolicyForAllo = typename Setup::template Policy; + using BuilderWithAllo = SeveralBuilder; + + return BuilderWithAllo(forward (args)...); + } + + + + + + /*********************************************************//** + * Entrance Point: start building a lib::Several instance + * @tparam I Interface type to use for element access + * @tparam E (optional) standard element implementation type + * @return a builder instance with methods to create or copy + * data elements to populate the container... + */ + template + SeveralBuilder + makeSeveral() + { + return SeveralBuilder{}; + } + template SeveralBuilder makeSeveral (std::initializer_list ili) @@ -665,13 +754,6 @@ namespace lib { .appendAll (ili); } - template - SeveralBuilder - makeSeveral() - { - return SeveralBuilder{}; - } - } // namespace lib #endif /*LIB_SEVERAL_BUILDER_H*/ diff --git a/src/lib/test/test-helper.hpp b/src/lib/test/test-helper.hpp index 0a58c8fc0..db7ec9a31 100644 --- a/src/lib/test/test-helper.hpp +++ b/src/lib/test/test-helper.hpp @@ -145,33 +145,6 @@ namespace test{ : "VAL"; } - /** helper for investigating a variadic argument pack - * @warning always spell out the template arguments explicitly - * when invoking this diagnostics, e.g. \c showVariadicTypes(args...) - * otherwise the template argument matching for functions might mess up the - * kind of reference you'll see in the diagnostics. - * @see test-helper-variadic-test.cpp - */ - template - inline string - showVariadicTypes () - { - return " :."; - } - - template - inline string - showVariadicTypes (X const& x, XS const&... xs) - { - return " :---#" - + boost::lexical_cast(1 + sizeof...(xs)) - + " -- Type: " + util::typeStr(x) - + " " + showRefKind() - + " Address* " + boost::lexical_cast(&x) - + "\n" - + showVariadicTypes (xs...); - } - /** @@ -195,6 +168,8 @@ namespace test{ + + namespace { // helper for printing type diagnostics template @@ -297,6 +272,36 @@ namespace test{ } + /** helper for investigating a variadic argument pack + * @remark you can pass arguments directly to this function, + * but be sure to use `std::forward(args)...` when forwarding + * universal references received from a variadic argument pack \a ARG. + * @note due to reference collapsing it is not possible to distinguish receiving + * a value from receiving a rvalue-reference. + * @see test-helper-variadic-test.cpp + */ + template + inline string + showVariadicTypes () + { + return " :."; + } + + template + inline string + showVariadicTypes (XX&& x, XS&&... xs) + { + return " :---#" + + util::toString (1 + sizeof...(xs)) + + " -- Type: " + showType() + + " \tAdr" + util::showAddr (x) + + "\n" + + showVariadicTypes (std::forward(xs)...); + } + + + + /** create a random but not insane Time value between 1s ... 10min + 500ms */ diff --git a/tests/12metaprogramming.tests b/tests/12metaprogramming.tests index 9af9413d1..05097e1e4 100644 --- a/tests/12metaprogramming.tests +++ b/tests/12metaprogramming.tests @@ -389,26 +389,23 @@ END TEST "Variadic template diagnostics" TestHelperVariadic_test <(d) <<"\n"; - cout << "--reference--\n" << showVariadicTypes(d) <<"\n"; - cout << "--move--\n" << showVariadicTypes(d) <<"\n"; + cout << "--reference--\n" << showVariadicTypes(d) <<"\n"; + cout << "--value--\n" << showVariadicTypes(makeRvalue()) <<"\n"; - forwardFunction("two values", "foo", 42L); // passed as REF, MOV - forwardFunction("matched", d,dr,std::move(dr)); // passed as REF, REF, MOV + forwardFunction("two values", "foo", 42L); // displayed as char [4] const&, long && + forwardFunction("references", d,cr,std::move(d)); // displayed as double&, double const&, double && - forwardFunction("baseclass", ref); + forwardFunction("baseclass", ref); // displayed as Interface const& } @@ -127,10 +126,8 @@ namespace test{ void forwardFunction (string id, ARGS&&... args) { - // in reality here you'd invoke some factory((args)...) - // cout << "--"<(args...) + << showVariadicTypes (std::forward(args)...) << "\n" ; } diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index 14c74f958..19334915f 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -82970,6 +82970,17 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
+ + + + +

+ Grundsätzlich ist der Allokator einfach durch die Belegung des 3.Template-Parameters festgelegt, das heißt, er steckt in der Policy — diese ist aber rein operational bestimmt, indem sie die richtigen Primitiv-Operationen mit der richtigen operationalen Semantik bereitstellt. Das ist keine gute Schnittstelle für den praktischen Gebrauch, und deshalb wird ein Hilfs-Schema über die Builder-Schnittstelle bereitgestellt; das erscheint eine sinnvolle Trennung zu sein, um diese zusätzliche Komplexität aus der Kernkomponente herauszuhalten +

+ +
+ +
@@ -83268,7 +83279,9 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- + + + @@ -83281,6 +83294,234 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+ enthält ein nested template / typedef: Policy +

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

+ Name: SetupSeveral +

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

+ Schema: Policy<I,E> +

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

+ Denn auch den Allocator verwendet man typischerweise an vielen Stellen, und nicht überall möchte man dann auch several-builder.hpp mit reinziehen; wäre also gut wenn es einfacher Template-Code ist, der mit entspr. Forward-Deklarationen auch »blank« vom Compiler akzeptiert wird +

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

+ template<class I, class E, template<typename> class ALO> +

+

+ struct AllocationPolicy; +

+ + +
+
+
+ + + + + +

+ template<template<typename> class ALO, typename...ARGS> +

+

+ struct SetupSeveral; +

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

+ SetupSeveral<std::void_t, lib::AllocationCluster&> +

+ + +
+
+ + + + +

+ Die Definition der Builder-Methode withAllocator<ALO>(args...)  sollte dazu führen, daß das Konfigurations-Template SetupSeveral  genau mit diesen Argumenten instantiiert wird... +

+ + +
+
+ + + + + + + + + + + +
+
+
+
+ +
@@ -83704,6 +83945,28 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
+ + + + + + +

+ braucht ein API zum Übernehmen eines bereits konstruierten Objekts +

+ + +
+ + + +

+ zunächst hatte ich AllocationCluster dafür definiert, daß die Objekte direkt im Cluster-Speicher erzeugt werden; hier aber liegt ein valider Fall vor, in dem das Objekt bereits erzeugt wurde (nämlich über den Builder), und von dort per move/copy in den Cluster übernommen werden soll +

+ +
+
+
@@ -83977,6 +84240,8 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
+ + @@ -84344,8 +84609,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
Und zwar weil sich aus der konkreten Implementierung diese Möglichkeit einfach ergibt

- - +