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
-
-
+