Parameterpack verändern
-
Hallo,
ich habe folgende Funktion:
template<typename ...Params> void execute( std::string const& name, Params&&... params ) { impl()->execute( name, std::forward<Params>( params )... ); }Ich möchte jetzt für std::string-Parameter den Wert verändern, gibt's da ne Möglichkeit?
-
-
mit eigenem Forwarder?
template<typename T> using reference_or_string_t = std::conditional_t<std::is_same_v<T&, std::string&>, std::string, T&&>; template<typename T> constexpr reference_or_string_t<T> my_forward(std::remove_reference_t<T>& t) { return std::forward<T>(t); } template<> std::string my_forward<std::string>(std::string& s) { return s + s; } template<> std::string my_forward<std::string&>(std::string& s) { return s + s; } template<typename ...Params> void execute( std::string const& name, Params&&... params ) { impl()->execute( name, my_forward<Params>( params )... ); }
-
@camper
Mal eine Frage, da ich das Problem selten habe.Was hälst du von der folgenden KI Antwort:
Du willst also innerhalb deiner variadischen Template‑Funktion bestimmte Parameter anders behandeln, sobald sie std::string sind. Das ist ein klassischer Fall für Template‑Überladung, SFINAE, oder if constexpr (C++17+).
Der Knackpunkt:
Params&&... nimmt alles entgegen, aber du kannst beim Weiterreichen unterscheiden, ob ein Parameter ein std::string ist und ihn vorher modifizieren.
Lösung: if constexpr + Parameter-Pack-Expansion
Wenn du C++17 hast, ist das die eleganteste Variante:template<typename ...Params> void execute(std::string const& name, Params&&... params) { impl()->execute( name, (modifyIfString(std::forward<Params>(params)))... ); } template<typename T> T&& modifyIfString(T&& value) { return std::forward<T>(value); } inline std::string modifyIfString(std::string value) { // Beispiel: String verändern value += "_modified"; return value; }Was passiert hier?
Für jeden Parameter wird modifyIfString(...) aufgerufen.Für Nicht‑Strings wird die generische Version gewählt → unverändert weitergereicht.
Für std::string wird die spezialisierte Overload gewählt → du kannst den Wert ändern.
Alternative: if constexpr direkt im Pack
Auch möglich:template<typename ...Params> void execute(std::string const& name, Params&&... params) { impl()->execute( name, ( [] (auto&& p) { using T = std::decay_t<decltype(p)>; if constexpr (std::is_same_v<T, std::string>) { return p + "_modified"; } else { return std::forward<decltype(p)>(p); } }(std::forward<Params>(params)) )... ); }🧩 Warum geht das nicht direkt in der Signatur?
Weil Params&&... perfekt weiterleitet und du nicht einfach einzelne Typen „abfangen“ kannst, ohne eine zusätzliche Schicht einzuziehen.
Du brauchst also entweder:eine Hilfsfunktion (modifyIfString), oder
ein Lambda mit if constexpr.
Wenn du Strings by reference ändern willst
Dann musst du aufpassen:
Variadische Templates + Forwarding können Referenzen verlieren, wenn du nicht sauber arbeitest.Beispiel für echte Mutation:
inline void modifyIfString(std::string& s) { s += "_changed"; } template<typename T> void modifyIfString(T&) {} template<typename ...Params> void execute(std::string const& name, Params&&... params) { (modifyIfString(params), ...); // Strings werden verändert impl()->execute(name, std::forward<Params>(params)...); }Wenn du mir sagst, wie genau du den String verändern willst (kopieren, mutieren, ersetzen, trimmen …), kann ich dir die optimale Variante dafür bauen.
-
Vielen Dank an euch beide, ich habe jetzt einen Mix aus campers und Quiche Lorraines Ansätzen. Das eigentlich Problem ist, dass ich ein db-Interface habe, das per Parameter-Pack Parameter entgegen nimmt. Wenn diese Parameter String-Parameter sind, dann möchte das db-Interface, dass die Strings in dem Encoding kodiert sind, wie es in den db-Verbindungsoptionen festgelegt wurde (üblicherweise UTF8). Um das nicht jedes Mal den Benutzer machen lassen zu müssen, habe ich eine Zwischenschicht eingeführt, die die Parameter Packs nach strings durchsucht und bei einem Treffer den string automatisch mit dem richtigen Encoding kodiert. Das sind leider nicht nur strings (
std::string&), sondern auchchar*,char[]in alle möglichenconstundvolatileVariationen. Meine gekürzte Lösung sieht jetzt so aus, den Boilerplate für die type_traits habe ich mal ausgelassen:template<typename T> bool constexpr replace_ref_v = is_string_ref_v<T> || is_char_array_ref_v<T> || is_char_pointer_ref_v<T>; template<typename T> using replacement_forward_type_t = std::conditional_t<replace_ref_v<T>, std::string, T&&>; template<typename T> replacement_forward_type_t<T> replace_forward( T& param ) { if constexpr( is_string_ref_v<T> ) { return std::string( "Replaced" ); } else if constexpr( is_char_array_ref_v<T> ) { return std::string( "Replaced" ); } else if constexpr( is_char_pointer_ref_v<T> ) { return std::string( "Replaced" ); } else { return std::forward<T>( param ); } } template<typename ...Params> void f1( Params&&... p ) { } template<typename ...Params> void f2( Params&&... p ) { f1( replace_forward<Params>( p )... ); }
-
@DocShoe sagte in Parameterpack verändern:
Ich denke du hast da noch einen Fehler drin:
template<typename T> replacement_forward_type_t<T> replace_forward( T& param )sollte stattessen:
template<typename T> replacement_forward_type_t<T> replace_forward( T&& param )Du willst hier eine Forwarding Reference haben, ansonsten wird die Referenz (
&oder&&nicht Teil des TypsT). Du machst also kein Forwarding, was du ja offensichtlich vorhast. Wenn du deiner Funktion z.B. eine Lvalue-Referenz übergibst:MyClass c1, c2; c2 = replace_forward(c1);dann gibt dein
replace_forwardviareplacement_forward_type_teineMyClass&&RValue-Referenz zurück. Wenn MyClass noch ein Move-Assignment hat, welches das Objekt auf der rechten Seite in einem ungültigen Zustand zurücklässt, kann das unangenehme Folgen haben. Du hast hier meines Erachtens nämlich keinforwardsondern eine Artmoveimplementiert.Ich denke du kannst das Ganze aber auch noch etwas vereinfachen und auf den
replacement_forward_type_t<T>komplett verzichten. Das was der Typ macht, kann man nämlich auch mitdecltype(auto)erreichen.template<typename T> auto replace_forward( T&& param ) -> decltyle(auto) { if constexpr( is_string_ref_v<T> ) { return std::string( "Replaced" ); } else if constexpr( is_char_array_ref_v<T> ) { return std::string( "Replaced" ); } else if constexpr( is_char_pointer_ref_v<T> ) { return std::string( "Replaced" ); } else { return std::forward<T>( param ); } }oder auch:
template<typename T> decltyle(auto) replace_forward(T&& param ) { ... }Beides ist äquivalent.
Deduktion mit
decltype(auto)erhält die Value Category des zurückgegebenen Wertes. D.h.return std::string("Replaced")gibt einen Wert vom Typstd::string&&zurück undreturn std::forward<T>(param)als was immer T deduziert wurde.
-
Dank dir, funktioniert und hat meinen Code leserlicher gemacht. TMP is echt ne Hausnummer, wenn man das nur selten benutzt.
-
@DocShoe sagte in Parameterpack verändern:
Dank dir, funktioniert und hat meinen Code leserlicher gemacht. TMP is echt ne Hausnummer, wenn man das nur selten benutzt.
Erstmal Deduktion versuchen und den Unterschied zwischen Deduktion via
autounddelctype(auto)kennen. Letzteres ist oft hilfreich, wenn man deduzieren und dabei die Referenz erhalten will.auto-Deduktion liefert dir nämlich immer den reinen Typ ohne die Referenz.
