Vorschläge zu: Funktionen mit variabler Argumentenzahl gleichen Typs



  • Hi,

    ich hab mir letztens Gedanken darüber gemacht, wie man am schönsten eine Funktion mit beliebig vielen Argumenten eines Types umsetzen kann (alle C++11 features erlaubt).

    Mit den ISO C Variadic Functions, meine Herren, möchte ich nichts zu tun haben.

    Meine Ideen

    :

    std::initializer_list + variadic macros

    Syntax:

    template<typename T>
    //Falls explizit ein Typ gebraucht wird, den Template-Parameter des Containers durch den gewünschten Typ ersetzen und den Template-Teil wegfallen lassen...
    void foo( std::vector<T> i )//Ein beliebiger STL-Container. Der std::initializer_list<value_type> const& CTor ist selbstverständlich bei keinem explicit.
    {
        //do some shit
    }
    
    #define foo(...) foo({__VA_ARGS__})
    

    Variadic templates + static assertions

    Das Grundgerüst:

    #include <iostream>
    #include <type_traits>
    #include <tuple>
    
    template<typename>
    constexpr bool all_same()
    {
            return true;
    };
    
    template<typename T, typename T2, typename... Ts>
    constexpr bool all_same();
    
    template<typename T, typename T2, typename... Ts>
    struct predicate
    {
            static constexpr bool value = std::is_same<T, T2>::value && all_same<T2, Ts...>();
    };
    
    template<typename T, typename T2, typename... Ts>
    constexpr bool all_same()
    {
            return predicate<T, T2, Ts...>::value;
    }
    
    template<typename ...T>
    using get_first = typename std::tuple_element<0, std::tuple<T...>>::type;
    
    template<typename ...T>
    void ensure_type_equality()
    {
            static_assert(all_same<T...>(), "All Template-Parameters have to have the same type!");
    }
    

    Syntax:

    template<typename ...intT>
    void foo(intT... args)
    {
            ensure_type_equality<intT...>();
    
            //opt. noch folgendes:
    
            std::vector<first_of<intT...>> v{ args... };//Und dann mit STL-Containern und Uniform-Initialization-Syntax/Dem initializer_list ctor arbeiten.
            //Auch hier kann der Typ (entweder durch static assertion oder durch den value_type des vectors) erzwungen werden.
    }
    

    Falls jemand eine Erklärung braucht, einfach nachfragen. Ich denke, die, die mir die Frage beantworten können (Kellerautomat, cooky, volkard (wo ist der Kerl), usw.) brauchen keine.

    Verwendung

    Verwendung beider Varianten ist offensichtlich:

    foo(1, 5, 8, 5, 7, myInt);
    

    Bei beiden gleich.

    ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

    Jetzt fragt sich, was die bessere, bzw. performantere Variante ist (ich denke, von der Performance dürften beide gleich schnell sein, abgesehen vom Compilieren).

    Ich würde ohne Frage obige Variante vorziehen, jedoch sieht die so nach "dirty hack" aus. Außerdem mag ich Makros nicht, variadic templates dagegen schon sehr.

    Gibt es noch weitere Möglichkeiten?

    MfG



  • Ich finde Funktionen mit variabler Anzahl an Parametern kacke. Gerade wenn sie gleichen Typs sind, dann ist ein Array oder vergleichbares sinnvoller. Ausnahmen bestaetigen die Regel.



    1. Makro? wäh. Wenn dann nimm die zwei geschweiften Klammern auf dich - und geh nicht erst den Umweg über vector oder ähnliches, initializer_list, reicht doch.
    template<typename T>
    void foo( std::initializer_list<T> l )
    {
        //do some shit
    }
    
    foo({1,2,meh});
    

    Evtl. um die Syntax etwas geschemidiger zu machen statt der Funktion ein struct mit entsprechendem Ctor mit Nebenwirkungen - obwohl ich nur für die gesparten Klammern nicht sowas unschönes basteln würde:

    struct foo
    {
      template<class T>
      foo(std::initializer_list<T> l)
      { /*do stuff*/ }
    };
    
    foo{1,2,meh};
    
    1. Ziemlicher Overkill oder? 😉
      Auch hier: warum den Umweg über vector? Du wirst eh in der Funktion eine Schleife über alle Elemente haben, dann ginge auch was in dieser Richtung:
    #include <iostream>
    template <class T> struct FooIt 
    { 
      void do_stuff(T t) { std::cout << t << ' '; }
    
      template<class U, class... Tail>
      void doFooForEach(U&& u, Tail&&... args)
      { 
        do_stuff(std::forward<U>(u));
        doFoo(std::forward<Tail>(args)...);
      }
      void doFooForEach() {}
    };
    
    template <class Head, class... T>
    void foo(Head&& h, T&&... args)
    { 
      FooIt<Head> theFooer; //<<- 1. parameter legt "Bearbeitungstyp" fest
      theFooer.doFooForEach(std::forward<Head>(h), std::forward<T>(args)...);
    }
    
    int main()
    {
      short meh = 3;
      foo(1,2u,meh);
      foo("sic!");
      foo(3.14, meh); //double, short -> kompatibel reicht ja wohl oder?
    }
    


  • pumuckl schrieb:

    1. Ziemlicher Overkill oder? 😉

    Hehe 😃

    pumuckl schrieb:

    Auch hier: warum den Umweg über vector?

    ICH HABE ZUERST INITIALIZER_LIST GENOMMEN!
    Aber dann dachte ich, ich will in der Funktion EH mit Sequentiellen Containern arbeiten, also... 🙂

    pumuckl schrieb:

    Du wirst eh in der Funktion eine Schleife über alle Elemente haben, dann ginge auch was in dieser Richtung:

    Ja, s.o. wie gesagt, ich wollte einen Sequentiellen Container.

    pumuckl schrieb:

    foo(3.14, meh); //double, short -> kompatibel reicht ja wohl oder?
    

    Dann statt is_same eben is_convertible ;
    Wäre prinzipiell mehr was ich wollte.

    Danke schonmal.



  • Um das schon vorweg zu nehmen:
    Das ist aus Spaß! Nichts, was ich in echten Programmen verwenden würde (da könnte man einfach ein start-end Iteratoren Paar nehmen).

    Einfach nur um zu sehen was geht.


  • Mod

    template<class U, class... Tail>
      void doFooForEach(U&& u, Tail&&... args)
      {
        do_stuff(std::forward<U>(u));
        doFoo(std::forward<Tail>(args)...);
      }
      void doFooForEach() {}
    

    wird zu

    template<typename Func, typename... T>
    void doForEach(Func f, T&&... args)
    {
        bool dummy[] = { (f(std::forward<T>(args)),false)..., false };
    }
    

    Perfect forwarding bedeutet ja nicht, dass der Aufruf plötzlich völlig kostenlos wird. Bei hinreichend vielen Parametern wird der Compiler vermutlich auch das Inlinen aufgeben.

    Ansonsten kann man so etwas in Erwägung ziehen:

    template <typename... T> struct all_convertible : std::true_type {};
    template <typename T, typename U, typename... V> struct all_convertible<T, U, V...> : std::integral_constant<bool, std::is_convertible<U, T>::value && all_convertible<T, V...>::value> {};
    
    template <typename T, typename... Args>
    auto fun(Args&&... x) -> typename std::enable_if<all_convertible<T, Args...>::value, T>::type;
    
    template <typename... T>
    auto fun(T&&... x) -> typename std::common_type<T...>::type
    {
        return fun<typename std::common_type<T...>::type>( std::forward<T>(x)... ); // der andere Overload ist spezieller
    }
    


  • camper schrieb:

    [...]wird zu

    template<typename Func, typename... T>
    void doForEach(Func f, T&&... args)
    {
        bool dummy[] = { (f(std::forward<T>(args)),false)..., false };
    }
    

    Njam 🙂


Anmelden zum Antworten