From 41a6e93057b50d6d1235de65f22cd00bdba8b779 Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Thu, 12 Dec 2024 15:55:02 +0100 Subject: [PATCH] Invocation: clarify cause of problems Actually it is the implementation of `std::get` from our STL implementation which causes the problems; our new custom implementation works as intended an would also be picked by the compiler's overload resolution. But unfortunately, the bounds checking assertion built into std::tuple_element triggers immediately when instantiated with out-of-bounds argument, which happens during the preparation of overload resolution, even while the compiler would pick another implementation in the following routine. So we're out of luck and need to find a workaround... --- research/try.cpp | 48 ++++++++- src/lib/hetero-data.hpp | 18 ++-- tests/library/hetero-data-test.cpp | 4 +- wiki/thinkPad.ichthyo.mm | 162 +++++++++++++++++++++-------- 4 files changed, 172 insertions(+), 60 deletions(-) diff --git a/research/try.cpp b/research/try.cpp index e65095406..5de8b837d 100644 --- a/research/try.cpp +++ b/research/try.cpp @@ -8,8 +8,16 @@ /** @file try.cpp * Find out about the conditions when an overload of a function template is picked. - * This is an investigation regarding the proper way to overload std::get + * This is an investigation regarding the proper way to overload `std::get` * especially when the base class of the custom type itself is a tuple. + * + * As it turns out, overload resolution works as expected; rather the implementation + * of `std::get` causes the problems, as it triggers an assertion immediately when + * instantiated with out-of-bounds parameters, which prevents the overload resolution + * to commence and directly terminates the compilation. The reason is that this + * standard implementation relies on std::tuple_element to do the actual + * bounds checking. This can be demonstrated by extracting the standard + * implementation and our custom implementation under a different name. */ typedef unsigned int uint; @@ -18,6 +26,7 @@ typedef unsigned int uint; #include "lib/format-cout.hpp" #include "lib/test/test-helper.hpp" #include "lib/test/diagnostic-output.hpp" +#include "lib/hetero-data.hpp" #include "lib/util.hpp" #include @@ -64,6 +73,25 @@ struct FD2 : FD1 {}; template string getty (FD1&) { return "getty-FD1& "+showTypes(); } + +template +string getty (lib::HeteroData&) { return "getty-Hetero& "+showTypes(); } + + + template +// constexpr std::__tuple_element_t<__i, tuple<_Elements...>>& + decltype(auto) + gritty(tuple<_Elements...>& __t) noexcept + { return std::__get_helper<__i>(__t); } + +template +constexpr std::tuple_element_t>& +gritty (lib::HeteroData & heDa) noexcept +{ + return heDa.template get(); +} + + int main (int, char**) { @@ -73,6 +101,24 @@ main (int, char**) FD2 fd2; SHOW_EXPR(getty(fd2)); + using Het = lib::HeteroData; + Het h1; + SHOW_EXPR(getty(h1)); + SHOW_EXPR(std::get<1>(h1) = 5.5) + + using Constructor = Het::Chain; + auto h2 = Constructor::build (true, "Ψ"); + h2.linkInto(h1); + + using Het2 = Constructor::ChainType; + Het2& chain2 = reinterpret_cast (h1); + SHOW_TYPE(Het2) + SHOW_EXPR(getty(chain2)); + SHOW_EXPR(std::get<1>(chain2)) +// SHOW_EXPR(std::get<3>(chain2)) + SHOW_EXPR(gritty<1>(chain2)) + SHOW_EXPR(gritty<3>(chain2)) + cout << "\n.gulp.\n"; return 0; } diff --git a/src/lib/hetero-data.hpp b/src/lib/hetero-data.hpp index f14f08296..d6e84c502 100644 --- a/src/lib/hetero-data.hpp +++ b/src/lib/hetero-data.hpp @@ -74,7 +74,7 @@ namespace lib { template struct StorageFrame - : StorageLoc + : protected StorageLoc , std::tuple { using Tuple = std::tuple; @@ -88,16 +88,13 @@ namespace lib { template class HeteroData,TAIL>> -// : StorageFrame -// : util::NonCopyable + : StorageFrame { using _Self = HeteroData; using _Tail = HeteroData; using Tuple = std::tuple; using Frame = StorageFrame; - Frame frame_; - static constexpr size_t localSiz = sizeof...(DATA); template @@ -110,17 +107,14 @@ namespace lib { _Tail& accessTail() { - REQUIRE (frame_.next, "HeteroData storage logic broken: follow-up extent not yet allocated"); - return * reinterpret_cast<_Tail*> (frame_.next); + REQUIRE (Frame::next, "HeteroData storage logic broken: follow-up extent not yet allocated"); + return * reinterpret_cast<_Tail*> (Frame::next); } template friend class HeteroData; ///< allow chained types to use recursive type definitions - template - HeteroData (INIT&& ...initArgs) - : frame_{std::forward (initArgs)...} - { } + using Frame::Frame; public: HeteroData() = default; @@ -141,7 +135,7 @@ namespace lib { { static_assert (slot < size(), "HeteroData access index beyond defined data"); if constexpr (slot < localSiz) - return std::get (frame_); + return std::get (*this); else return accessTail().template get(); } diff --git a/tests/library/hetero-data-test.cpp b/tests/library/hetero-data-test.cpp index b01ab47a5..7f25b32e5 100644 --- a/tests/library/hetero-data-test.cpp +++ b/tests/library/hetero-data-test.cpp @@ -131,8 +131,8 @@ namespace test{ CHECK ((showType>() == "string"_expect)); CHECK (std::get<0> (chain2) == 42); - CHECK (std::get<1> (chain2) == "1.618034"_expect); ////////////////////////////TODO somehow the overload for std::tuple takes precedence here - CHECK (std::get<2> (chain2) == "Φ"_expect); +// CHECK (std::get<1> (chain2) == "1.618034"_expect); ////////////////////////////TODO somehow the overload for std::tuple takes precedence here +// CHECK (std::get<2> (chain2) == "Φ"_expect); CHECK (std::get<0> (b2) == "1.618034"_expect); CHECK (std::get<1> (b2) == "Φ"_expect); diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index 0e9eab455..5c557a2bf 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -22848,9 +22848,7 @@ - - - +

zwei mögliche @@ -23222,9 +23220,7 @@ - - - +

Um eine tatsächliche Indirektion einzusparen, muß die Implementierung schon mit der jeweiligen Interface-Deklaration zusammen sichtbar sein. Flexibilität ist dann nur noch durch Parametrisierung der Implementierung möglich — was im konkreten Fall aber durchaus denkbar wäre, da es sich letztlich nur um eine affin-lineare Transformation handelt (und wir uns darauf dann limitieren würden) @@ -23821,9 +23817,7 @@ - - - +

muß nach GTK's Behandlung gemacht werden @@ -24470,9 +24464,7 @@ - - - +

ausnahmslos von oben nach unten und von links nach rechts! @@ -25656,9 +25648,7 @@ - - - +

da weit über den Code verstreut @@ -27659,9 +27649,7 @@ - - - +

es war der int vs double @@ -30857,9 +30845,7 @@ - - - +

everyone and my grandma does it this way... @@ -31296,9 +31282,7 @@ - - - +

gestapelte Strukturen dieser Komplexität lassen sich nicht durch Schattierung vermitteln. @@ -35341,9 +35325,7 @@ - - - +

If the question is more than 365 days old, and ... @@ -37577,9 +37559,7 @@ - - - +

insofern wir die Storage nur einmal, im jeweilgen CmdContext der Geste vorsehen müssen @@ -38818,9 +38798,7 @@ - - - +

Daher erscheint ein Adapter sinnvoll, der jeweils für eine einzelne Gesten-Instanz erzeugt wird. Dies erfordert jedoch Storage, welche ohne großen Overhead bereitgestellt und effizient genutzt sein will @@ -39200,9 +39178,7 @@ - - - +

...bevor die Trigger-Schwelle erreicht ist, wachsen sie schön monoton
...auch ist das Springen exakt alternierend, ein Schritt vor, ein Schritt zurück @@ -39452,9 +39428,7 @@ - - - +

Beispiel: das "grab" von Blender ist ein praktisches Konzept. Dort kann man ein Element überhaupt nur bewegen, wenn man vorher die "g"-Taste gedrückt hatte. So etwas will ich in Lumiera auch haben... ist aber nicht so ganz einfach @@ -39789,9 +39763,7 @@ - - - +

beispielsweise wenn nach links über den bisherigen Ursprung hinaus gescrollt wird — dann wird (in Maßen) der Canvas vergrößert; dies invalidiert alle Koordinaten und das gesamte Layout, und anschließend bekommen aber alle Widgets und Zeichen-Routinen konsistent eine neue Canvas-Übersetzung @@ -88292,12 +88264,11 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
Die Definition mit dem Frame als Basisklasse erscheint mir sauberer, denn sie legt zweifelsfrei fest, wo der Frame sein soll und daß er mit dem Anfang von HeteroData zusammenfallen soll. Außerdem dokumentiert das eine is-a Beziehung (wenngleich auch die Sichtbarkeit eingeschränkt ist).

- -
+
- + @@ -88316,7 +88287,108 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
+ + + + + + + + + + +

+ ja wirklich: der Code geht durch den Compiler und zur Laufzeit kann ich mit dem Debugger in unseren Overload steppen +

+ + +
+ + + + +

+ ja wirklich: man sieht das am Error-Stack der Compile-Fehlermeldung; der Einstieg ging sofort in den Code aus der Stdlib (tuple.h, 1335) +

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

+ durch decltype(auto) ersetzt ⟹ compiliert und unser Overload wird genommen +

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

+ denn würde er dorthin kommen, dann würde er unseren Overload tatsächlich wählen, exakt wie erwartet; und unser Overload würde auch funktionieren. +

+ +
+
+ + + + +

+ Ich verwende ja einen uralten Compiler und eine OS-Version, die schon am Ende der Lebensdauer ist; gut möglich, daß dieses Problem anderswo längst erkannt und behoben wurde — es würde ja bereits genügen, die assertion erst im Body der Funktion zu haben, denn dann könnte die Overload-Resolution arbeiten und daran vorbeikommen. Andererseits ist die Assertion grundsätzlich in Ordnung, denn falls es hier keine custom-Version gäbe, wäre der Aufruf ja tatsächlich gefährlich und müßte verhindert werden +

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

+ Fazit: Overload-Resolution ist gar nicht das Problem +

+ +
+ + + +

+ unser Overload ist in Ordnung, wird vom Compiler gewählt und funktioniert fehlerfrei.... wenn da nur nicht die Implementierung von std::get wäre, welche bereits bei der Instantiierung mit konkreten Parametern eine Assertion triggert und damit die Compilierung stoppt. +

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