Container generisch befüllen?
-
Hallo,
angenommen ich habe so einen Code:
#include <vector> #include <set> template <typename T, typename container = std::vector<T>> class test { container data; public: void add_data(T const &value) { data.push_back(value); } }; int main() { test<int> t1; // Container ist default, also std::vector<int> t1.add_data(1); // Geht test<int, std::set<int>> t2; // Container ist std::set<int> t2.add_data(1); // Geht nicht }
Dann erhalte ich für
t2.add_data(1);
eine Fehlermeldung, nämlich dassstd::set
kein member namenspush_back
hat. Ist ja auch logisch, dastd::set
dieses member wirklich nicht hat.Ich frage mich jetzt nur, wie man das am besten machen könnte? Also generisch für einen beliebigen Container der STL bzw. für benutzerdefinierte Container die deren Interface einhalten?
-
Mit einer freien Funktion, die für alle Container überladen wird.
-
Außerhalb der Klasse nutzt du SFINAE:
#define AUTO_RETURN(...) -> decltype(__VA_ARGS__) {return (__VA_ARGS__);} template <typename Container, typename U> auto add_data_to(Container& C, U&& value) AUTO_RETURN(C.insert(std::forward<U>(value))) template <typename Container, typename U> auto add_data_to(Container& C, U&& value) AUTO_RETURN(C.push_back(std::forward<U>(value)))
Und innerhalb dann
void add_data(T const& value) {add_data_to(data, value);}
-
Ok, vielen Dank schonal.
@Arcoth:
Könntest du mir noch erklären warum man hier das U&& braucht? Also die && vor dem U?
Reicht da nicht eine "normale" const &? Und sollte diese doppelte Referenz nicht auch const sein, also U const &&?Hab das schon ein paar mal gesehen, aber bis jetzt noch nie selbst gebraucht (wird anscheinend Zeit dafür)
-
Man braucht es hier nicht, es gibt aber keinen Grund das Template nicht gleich allgemein zu schreiben.
Solche Parameter nennt man Universal References. Sie können jeden beliebigen Ausdruck per Referenz entgegen nehmen - sowohl lvalues als auch rvalues.
Es gibt die reference collapsing Regel:
[dcl.ref]/6 schrieb:
If a typedef (7.1.3), a type template-parameter (14.3.1) or a decltype-specifier (7.1.6.2) denotes a type
TR
that is a reference to a typeT
, an attempt to create the type “lvalue reference to cvTR
” creates the type “lvalue reference toT
”, while an attempt to create the type “rvalue reference to cvTR
” creates the typeTR
.Sprich: Für ein Funktionstemplate der Form
template <typename T> void F(T&&); F(1); // (1) int i; F(i); // (2)
Kann für
T
ein Objekttyp deduziert werden - bspw.int
, wie in (1). Es kann aber auch ein Referenztyp deduziert werden, in (2) ist dasint&
. Laut obiger Regel wird dementsprechend der Typ des Parameters ebenfallsint&
.Probiere mal etwas mit diesem Funktionstemplate herum:
template <typename T> void F(T&&) { std::cout << __PRETTY_FUNCTION__ << '\n'; }
Übergib' rvalues und lvalues, auch
const
-Objekte, und schau was für Typen deduziert werden.
FürF(1); int i; int const j = 5; F(i); F(j);
Erhalte ich
void F(T&&) [with T = int] void F(T&&) [with T = int&] void F(T&&) [with T = const int&]
std::forward
stellt sicher dassu
dann mit derselben Wertkategorie weitergeleitet wird wie es empfangen wurde. War das Argument also ein rvalue wie in (1), iststd::forward<U>(u)
auch ein rvalue, war es ein lvalue wie in (2).. den Rest kennst du. Das ganze nennt sich perfect forwarding - wir leiten ein Argument genauso weiter wie wir es bekommen haben, ohne Kopien oder Verlust der Wertkategorie.Derselbe Trick funktioniert für
U const&&
aber nicht mehr - fürU
darf dann nämlich keine Referenz mehr deduziert werden.
-
Ok, vielen Dank für die ausführliche Erklärung
Werde mich mit deinen Beispielen auseinandersetzten, aber das klingt schonmal ganz gut.
-
Hallo nochmal,
also ich hab mittlerweile deine Lösung getestet und leider funktioniert das bei mir nicht
Also ich habe das genauso eingefügt wie von dir gepostet, allerdings erhalte ich die Fehlermeldung "error C2995: "unknown-type add_data_to(Container &,U &&)": Funktionsvorlage wurde bereits definiert.".
Das komische ist, bei Ideone kann ich es problemlos kompilieren, mein VS 2013 scheint es dagegen nicht hinzukriegen.
Ist das irgendeine spezielle/neuartige Technik die du da verwendet hast und deswegen noch nicht in VS angekommen ist, oder ist das ein bug?
-
Mir fällt gerade noch ein. Herr STL hat doch mal ein Printer Programm geschrieben, das den Inhalt eines jeden Containers ausgibt. Da hat er auch zwischen verschiedenen Containerarten unterschieden. Vielleicht hilft dir das und du kannst auf seiner Basis aufbauen.
und
https://onedrive.live.com/?cid=E66E02DC83EFB165&id=E66E02DC83EFB165!293
-
Ich würde mich erstmal fragen, inwiefern das sinnvoll ist. Für was genau gedenkst du, das einzusetzen? Zwischen dem Einfügen eines Elementes in einen vector und in ein set besteht semantisch ein dermaßen großer Unterschied, dass ich mir kaum einen Fall vorstellen kann, an dem man wirklich ein einheitliches Interface zum Einfügen von Elementen direkt in den rohen Container haben will...
-
Warum nicht einfach einen Iterator bereitstellen? Je nach Container std::inserter oder std::back_inserter. Die Syntax darauf ist dann einheitlich.
-
Arcoth schrieb:
Universal References
Stelle aus dem Standard bitte, wo das definiert wird.
-
Kellerautomat schrieb:
Arcoth schrieb:
Universal References
Stelle aus dem Standard bitte, wo das definiert wird.
Ich glaube das ist eine Wortschöpfung von Scott Meyers:
http://channel9.msdn.com/Events/GoingNative/2013/An-Effective-Cpp11-14-Sampler
-
Kellerautomat schrieb:
Arcoth schrieb:
Universal References
Stelle aus dem Standard bitte, wo das definiert wird.
Genau genommen sinds Forwarding References.
N4164 §8.3.2/6 schrieb:
If a typedef-name (7.1.3, 14.1) or a decltype-specifier (7.1.6.2) denotes a type TR that is a reference to a type T, an attempt to create the type “lvalue reference to cv TR” creates the type “lvalue reference to T”, while an attempt to create the type “rvalue reference to cv TR” is called a forwarding reference and creates the type TR.
-
Kellerautomat schrieb:
Arcoth schrieb:
Universal References
Stelle aus dem Standard bitte, wo das definiert wird.
Was genau? Der Begriff?