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 dass std::set kein member namens push_back hat. Ist ja auch logisch, da std::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.


  • Mod

    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) 😮


  • Mod

    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 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 ” creates the type TR .

    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 das int& . Laut obiger Regel wird dementsprechend der Typ des Parameters ebenfalls int& .

    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ür

    F(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 dass u dann mit derselben Wertkategorie weitergeleitet wird wie es empfangen wurde. War das Argument also ein rvalue wie in (1), ist std::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ür U 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.

    http://channel9.msdn.com/Series/C9-Lectures-Stephan-T-Lavavej-Advanced-STL/C9-Lectures-Stephan-T-Lavavej-Advanced-STL-6-of-n

    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.

    https://isocpp.org/blog/2014/10/n4164


  • Mod

    Kellerautomat schrieb:

    Arcoth schrieb:

    Universal References

    Stelle aus dem Standard bitte, wo das definiert wird.

    Was genau? Der Begriff?


Log in to reply