Template, Concepts ok?
-
Moin,
ich habe folgende Funktion geschrieben mit welcher man zwei Vektoren vereinigen kann.
template<typename T> concept UnionContConcept = requires(T a) { { a.size() } -> std::convertible_to<std::size_t>; { a.resize(static_cast<size_t>(42)) }; { std::random_access_iterator<decltype(std::begin(a))> }; }; /** ******************************************************************************* * \FUNCTION void AlgExt_Union(Cont& M1, size_t Index, const Cont& M2) * * \DESCRIPTION * Fügt die Menge M2 ab der Stelle Index in M1 ein. * * Abhängig von der Größe von M2 und dem Index, überschreibt die Funktion * Werte in M1 oder fügt diese M1 hinzu. * * Beispiele: * * | M1 | Index | M2 | Ergebnis (M1) | * |-----------------------------|-------|-----------|-----------------------------| * | (1, 2, 3) | 0 | (9) | (9, 2, 3) | * | (1, 2, 3) | 4 | (2) | (1, 2, 3, 2) | * | (1, 2, 3) | 1 | (4, 5, 6) | (1, 4, 5, 6) | * | (1, 2, 3, 4, 5, 6, 7, 8, 9) | 1 | (9 9) | (1, 9, 9, 4, 5, 6, 7, 8, 9) | ******************************************************************************* **/ template<UnionContConcept Cont> void AlgExt_Union(Cont& M1, size_t Index, const Cont& M2) { if (Index < M1.size()) { if (Index + M2.size() > M1.size()) M1.resize(Index + M2.size()); std::ranges::copy(M2, std::begin(M1) + Index); } else { std::ranges::copy(M2, std::back_inserter(M1)); } }
Da ich noch nicht fit in Sachen Concepts bin, möchte ich deswegen mal nachfragen, ob es hierzu Verbesserungsvorschläge gibt.
-
@Quiche-Lorraine sagte in Template, Concepts ok?:
template<typename T>
concept UnionContConcept = requires(T a)
{
{ a.size() } -> std::convertible_tostd::size_t;
{ a.resize(static_cast<size_t>(42)) };
{ std::random_access_iterator<decltype(std::begin(a))> };
};Hey, das ist noch ein wenig holprig.
template<typename T> concept UnionContConcept = requires(T a, std::size_t size) { { a.size() } -> std::convertible_to<std::size_t>; { a.resize(size) }; requires std::ranges::random_access_range<T>; };
oder alternativ
template<typename T> concept UnionContConcept = std::ranges::random_access_range<T> && std::ranges::sized_range<T> && requires(T a, std::size_t size) { { a.resize(size) }; };
Sry, dass ich so kurz angebunden bin aber wenn du Fragen hast, dann beantworte ich die gerne.
-
Ich hätte andere Anmerkungen:
Aus
concept UnionContConcept
hätte ich
concept UnionContainer
gemacht.
Grund: Bei "Cont" war mir nicht sofort klar, was du meinst. Cont = Content? Continue? Von daher sehe ich Grund dafür, das auszuschreiben. Ist es vielleicht sogar ein "UnionableContainer"? Klingt komisch, vielleicht "MergeableContainer"?Sind es überhaupt "Container"? Im Docstring steht was von "Fügt die Menge M2 ab der Stelle Index in M1 ein." - aber um Mengen handelt es sich keinesfalls, denn dann würde ja sowas wie
std::set
Sinn ergeben - Mengen haben auch keine Ordnung (und keine Dopplungen). Daher ist das Wording auf jeden Fall verbesserbar. Dein Code funktioniert ja gerade nicht mit Mengen. Von daher wäre vielleicht das Wort "Sequence" besser geeignet. ->concept MergableSequence
? Gutes Naming ist schwierig!if (Index + M2.size() > M1.size())
Bei solchem Code habe ich immer Angst vor wrap around der Addition.M1.resize(Index + M2.size());
- resize legt dir alle Elemente erstmal an (erfordert u.a. einen Default-Konstruktor). Je nach dem, wie teuer das ist, wäre vielleichtreserve
eine Option nebst Änderung des Codes, sodass das alles wieder passt?
-
@Quiche-Lorraine @DNKpp Funktioniert das Constraint
{ a.resize(size) }
eigentlich mit allen erlaubten Size-Typen? Das muss ja nicht unbedingt einsize_t
sein, und dass der Typ "convertible to"size_t
ist, heißt ja nicht zwangsläufig, dass das auch umgekehrt gilt (obwohl so ein Typ wohl recht exotisch sein müsste). Ich frage mich gerade, ob es theoretisch auch einen sinnvollen Typen geben kann, wo das nicht greift. 8- und 16-bit Integer-Typen sollten eigentlich gehen, aber ich hätte das wahrscheinlich generischer alsdecltype(a.size())
formuliert. macht natürlich den Code unübersichtlicher und es ist fraglich, ob man es wirklich braucht. Ich hab mich selbst schon öfter in eigenem Code verlaufen, der einfach zu generisch warAchja, und
std::ranges::copy
undstd::back_inserter
sind soweit ich weissconstexpr
(ebensostd::vector
und seine Funktionen ab C++20 ), da wäre es fein, wenn die Union-Funktion das ebenfalls sein könnte.
-
@Finnegan sagte in Template, Concepts ok?:
Funktioniert das Constraint{ a.resize(size) } eigentlich mit allen erlaubten Size-Typen?
Die Frage ist, hast du Container für die das nicht der Fall ist? Ich kenne den use-case nicht. Wenn es Unterschiede gibt, dann wäre es durchaus eine Möglichkeit
Container::size_type
statt plainstd:size_t
zu verwenden, oder den return von.size()
zu ermitteln. Wenn das nicht einheitlich verfügbar ist, dann muss man sich wohl eine trait-struktur basteln.
Wenn man hinter alle diese Annahmen ein Fragezeichen stellt, dann wird das sehr schnell sehr komplex.- Was ist, wenn es kein
.size()
gibt sondern.get_size()
? - Oder
.resize()
heißt.set_size()
?
Concepts sind nicht dazu da, alle möglichen Fälle zu abzudecken, sondern um constraints einheitlich abfragen zu können. Wenn diese von dem Typen nicht erfüllbar sind, dann funktioniert auch deine Funktion nicht, für die du das Concept entworfen hast.
Jedenfalls bezieht
std::convertible_to
explizite Konvertierungen mit ein, daher müsste der Type wohl sehr exotisch sein, dass das nicht funktioniert.
- Was ist, wenn es kein
-
@DNKpp sagte in Template, Concepts ok?:
@Finnegan sagte in Template, Concepts ok?:
Funktioniert das Constraint{ a.resize(size) } eigentlich mit allen erlaubten Size-Typen?
Die Frage ist, hast du Container für die das nicht der Fall ist? Ich kenne den use-case nicht. Wenn es Unterschiede gibt, dann wäre es durchaus eine Möglichkeit
Container::size_type
statt plainstd:size_t
zu verwenden, oder den return von.size()
zu ermitteln. Wenn das nicht einheitlich verfügbar ist, dann muss man sich wohl eine trait-struktur basteln.Nein, der Einwand ist nur theoretischer Natur. std::size sieht z.B. auch so aus:
constexpr auto size( const C& c ) -> decltype(c.size());
. Intuitiv hätte ich das also erstmal generisch gehalten. Daher frage ich jaJedenfalls bezieht
std::convertible_to
explizite Konvertierungen mit ein, daher müsste der Type wohl sehr exotisch sein, dass das nicht funktioniert.Denke ich auch. Sich auf den von
a.size()
zurückgegebenen Typen zu beziehen empfinde ich allerdings intuitiv erstmal als generischer. Aber da greift das Argument mit dem extra Code für wenig konkreten Nutzen. Wenn es aber doch etwas geben sollte, das einem erstmal nicht einfällt, dann wäre man damit dennoch fein raus
-
@All
Danke für eure Tips bzw. Diskussion.
Danke für dein Angebot.
Ich bin da noch dabei die einzelnen Elemente zu verstehen. Rein gedanklich verstehe ich
std::ranges::random_access_range
bzw.std::random_access_iterator
. Etwas verwundert bin ich aber über die Definition aus https://en.cppreference.com/w/cpp/iterator/random_access_iterator.Da steht z.B.
std::totally_ordered<I>
. Das sieht so aus als wäre I der Iteratortyp. Aber wie leitet der Compiler diesen her?#include <vector> #include <iterator> #include <list> template<typename T> concept FooConcept = std::ranges::random_access_range<T>; class ABC { public: double x = 0; double y = 0; double z = 0; bool operator<(const ABC&) const = delete; }; template<FooConcept T> void Test(const T&) { } // Testprogramm int main() { std::vector<ABC> L1 { {1, 2, 3} }; std::list<ABC> L2 { {1, 2, 3} }; Test(L1); Test(L2); // Verstehe ich, wird von Intellisense markiert return 0; }
Gutes Naming ist schwierig!
Oh ja, das Problem kenne ich zur Genüge.
Ich bin da noch etwas unentschlossen.
MergableSequence
erinnert mich irgentwie anstd::merge
.
-
@Quiche-Lorraine sagte in Template, Concepts ok?:
@DNKpp:
Danke für dein Angebot.
Ich bin da noch dabei die einzelnen Elemente zu verstehen. Rein gedanklich verstehe ich std::ranges::random_access_range bzw. std::random_access_iterator. Etwas verwundert bin ich aber über die Definition aus https://en.cppreference.com/w/cpp/iterator/random_access_iterator.
Da steht z.B. std::totally_ordered<I>. Das sieht so aus als wäre I der Iteratortyp. Aber wie leitet der Compiler diesen her?Was genau meinst du mit "Aber wie leitet der Compiler diesen her?"
Wunderst du dich, wie der Compiler den Iterator-Typ des Containers ermittelt?I
ist an dieser Stelle nichts weiter als eine Template-Argument, welches das Concept verlangt.template< class T > concept random_access_range = ranges::bidirectional_range<T> && std::random_access_iterator<ranges::iterator_t<T>>;
https://en.cppreference.com/w/cpp/ranges/random_access_range
Wie du siehst, extrahiert
random_access_range
mittels desstd::ranges::iterator
traits den Iterator-Typ, welches interndecltype(ranges::begin(std::declval<T&>()))
benutzt.
-
@DNKpp sagte in Template, Concepts ok?:
Wunderst du dich, wie der Compiler den Iterator-Typ des Containers ermittelt?
Genau
-
Danke für deine Hilfe! Habs verstanden.
Habe vergessen
std::ranges::random_access_range
genau anzuschauen und daher dies übersehen.
-
@Quiche-Lorraine sagte in Template, Concepts ok?:
Gutes Naming ist schwierig!
Oh ja, das Problem kenne ich zur Genüge.
Ich bin da noch etwas unentschlossen. MergableSequence erinnert mich irgentwie an std::merge.Ja, ich war mit MergableSequence auch nicht zufrieden. Gibt es überhaupt Sequences, die nicht mergeable sind? Von daher müsste vielleicht irgendwas rein, weswegen du dieses Concept überhaupt machst. Bzw. irgendwie sollte so klar wie möglich sein, was dieses Concept eigentlich erreicht. Was ist es mehr als ein Container mit Random Access Iterator?
Für wichtige Namen, die wir sogar mal im Team diskutiert haben (da muss man dann wieder aufpassen, dass man nicht zu viel Zeit versenkt), habe ich dann sogar mal ChatGPT gefragt und tatsächlich auch mal gute Ideen bekommen, an die niemand im Team gedacht hatte, die aber eigentlich viel besser und klarer waren. Und nachdem wir dann ein größeres Rename gemacht hatten, fiel sofort schon vom sprachlichen her ein Programmierfehler auf!
Aber diese Diskussion führt ja an dem eigentlichen Problem etwas vorbei, ich wurde nur vom Begriff der "Menge" getriggert