variadic return-Wert



  • Hallo zusammen,

    ich möchte über zwei Tupel parallel laufen und die beiden Werte paarweise dividieren.
    Der Typ richtet sich nach dem Typ im ersten Tupel.
    Einigen kommt ein ähnliches Problem wahrscheinlich aus einem vorherigen Thread bekannt vor.

    Aus diesem Thread habe ich dann folgendes abgeleitet:

    #include<iostream>
    #include<tuple>
    
    template<int I, int Max>
    class Printer{
    public:
            template<typename T1, typename T2>
            static void print(const T1& t1, const T2& t2){
                    typedef typename std::tuple_element<I,T1>::type type1;
                    typedef typename std::tuple_element<I,T2>::type type2;
                    type1 t1v = std::get<I>(t1);
                    type2 t2v = std::get<I>(t2);
                    std::cout << t1v << ' ' << t2v << ' ' << static_cast<type1>(t1v/t2v) << std::endl;
                    Printer<I+1, Max>::print(t1, t2);
            }
    };
    
    template<int I>
    class Printer<I, I>{
    public:
            template<typename T1, typename T2>
            static void print(const T1& t1, const T2& t2){}
    };
    
    template<typename... T1, typename... T2>
    void print(const std::tuple<T1...>& t1, const std::tuple<T2...>& t2){
            Printer<0, std::tuple_size<std::tuple<T1...> >::value>::print(t1, t2);
    }
    
    int main(){
            typedef std::tuple<float, int, int> tuple_type1;
            tuple_type1 t1(1.2, 12, 5);
            typedef std::tuple<int, double, int> tuple_type2;
            tuple_type2 t2(2, 2.1, 2);
            print(t1, t2);
    }
    

    Hier wird ja in der Funktion das Ergebnis ausgegeben.
    Jetzt möchte ich aber ein Ergebnis-Tupel haben, das ich dann ausgebe.

    So weit so gut...
    Nun reiche ich halt einfach ein drittes Tupel mit und übergebe es:

    #include<iostream>
    #include<tuple>
    
    template<int I, int Max>
    class Printer{
    public:
            template<typename T1, typename T2, typename T3>
            static void print(const T1& t1, const T2& t2, T3& t3){
                    typedef typename std::tuple_element<I,T1>::type type1;
                    typedef typename std::tuple_element<I,T2>::type type2;
                    type1 t1v = std::get<I>(t1);
                    type2 t2v = std::get<I>(t2);
                    std::get<I>(t3) = static_cast<type1>(t1v/t2v);
                    Printer<I+1, Max>::print(t1, t2, t3);
            }
    };
    
    template<int I>
    class Printer<I, I>{
    public:
            template<typename T1, typename T2, typename T3>
            static void print(const T1& t1, const T2& t2, T3& t3){}
    };
    
    template<typename... T1, typename... T2, typename... T3>
    void print(const std::tuple<T1...>& t1, const std::tuple<T2...>& t2, std::tuple<T3...>& t3){
            Printer<0, std::tuple_size<std::tuple<T1...> >::value>::print(t1, t2, t3);
    }
    
    int main(){
            typedef std::tuple<float, int, int> tuple_type1;
            tuple_type1 t1(1.2, 12, 5);
            typedef std::tuple<int, double, int> tuple_type2;
            tuple_type2 t2(2, 2.1, 2);
            tuple_type1 t3;
            print(t1, t2, t3);
            std::cout << std::get<0>(t3) << ' ' << std::get<1>(t3) << ' ' << std::get<2>(t3) << std::endl;
    }
    

    So hatte ich es mir jedenfalls gedacht, jedoch habe ich in meinem wirklichen Fall z. B. ein boost::ref<float> in meinem Tupel. Und jetzt bin ich mit meinem Latein am Ende, denn boost::ref<float> hat ja keinen Default-Konstruktor und somit kann ich kein 'leeres' Tupel erstellen.

    Was würdet ihr dann machen?
    Also ich bräuchte ja etwas wie variadic return-Werte... Gibts sowas?
    Welche Ideen habt ihr sonst?
    Also ich möchte (wenn möglich) ein std::tuple als Ergebnis und keine eigene Datenstruktur

    Gruß,
    XSpille



  • Ich glaub, ich weiß wie ich es hinkriegen kann...

    Mit std::tuple_cat

    EDIT: Aber trotzdem würde ich gerne wissen, ob man einen variadic return-Wert machen kann 🙂



  • Falls jemand zukünftig etwas Ähnliches vor hat:

    #include<iostream> 
    #include<tuple> 
    
    template<typename T, typename T2>
    struct PrependType;
    
    template<typename T, typename ...Args>
    struct PrependType<T, std::tuple<Args...> >
    {
    	typedef typename std::tuple<T, Args...> type;
    };
    
    template<int I, int Max, typename T1, typename T2> 
    class Printer{
    public:
    	typedef typename std::tuple_element<I,T1>::type type;
    	typedef typename PrependType<type, typename Printer<I+1,Max, T1, T2>::tuple_type >::type tuple_type;
    
    	static tuple_type print(const T1& t1, const T2& t2){ 
    		type t = static_cast<type>(std::get<I>(t1)/std::get<I>(t2));
    		return std::tuple_cat(std::tuple<type>(t), Printer<I+1, Max, T1, T2>::print(t1, t2));
    	} 
    }; 
    
    template<int I, typename T1, typename T2> 
    class Printer<I, I, T1, T2>{ 
    public:
    	typedef std::tuple<> tuple_type;
    
    	static std::tuple<> print(const T1& t1, const T2& t2){ return std::tuple<>();} 
    }; 
    
    template<typename... T1, typename... T2> 
    typename Printer<0, std::tuple_size<std::tuple<T1...> >::value, std::tuple<T1...>, std::tuple<T2...> >::tuple_type print(const std::tuple<T1...>& t1, const std::tuple<T2...>& t2){ 
    	return Printer<0, std::tuple_size<std::tuple<T1...> >::value, std::tuple<T1...>, std::tuple<T2...> >::print(t1, t2); 
    } 
    
    int main(){ 
    	typedef std::tuple<float, int, int> tuple_type1; 
    	tuple_type1 t1(1.2, 12, 5); 
    	typedef std::tuple<int, double, int> tuple_type2; 
    	tuple_type2 t2(2, 2.1, 2); 
    	tuple_type1 res = print(t1, t2);
    	std::cout << std::get<0>(res) << ' ' << std::get<1>(res) << ' ' << std::get<2>(res) << std::endl;
    }
    

    Gruß,
    XSpille



  • oder alternativ mit Ergebnissen als variadic-Parameter nach unten reichen:

    #include<iostream> 
    #include<tuple>
    
    template<int I, int Max> 
    class Printer{ 
    public:
        template<typename T1, typename T2, typename... T3>
        static T1 print(const T1& t1, const T2& t2, T3&&... t3){ 
            typedef typename std::tuple_element<I, T1>::type type;
    		type t = static_cast<type>(std::get<I>(t1)/std::get<I>(t2));
            return Printer<I+1, Max>::print(t1, t2, std::forward<T3>(t3)..., std::forward<type>(t));
    	}
    }; 
    
    template<int I> 
    class Printer<I, I>{ 
    public: 
    
    	template<typename T1, typename T2, typename... T3>
        static T1 print(const T1& t1, const T2& t2, T3&&... t3){
    		return std::tuple<T3...>(std::forward<T3>(t3)...);} 
    }; 
    
    template<typename... T1, typename... T2> 
    typename std::tuple<T1...> print(const std::tuple<T1...>& t1, const std::tuple<T2...>& t2){ 
        return Printer<0, std::tuple_size<std::tuple<T1...> >::value>::print(t1, t2); 
    } 
    
    int main(){ 
        typedef std::tuple<float, int, int> tuple_type1; 
        tuple_type1 t1(1.2, 12, 5); 
        typedef std::tuple<int, double, int> tuple_type2; 
        tuple_type2 t2(2, 2.1, 2);
        tuple_type1 res(print(t1, t2));
        std::cout << std::get<0>(res) << ' ' << std::get<1>(res) << ' ' << std::get<2>(res) << std::endl; 
    }
    

    EDIT: Leider wird bei beiden Möglichkeiten sehr viel gemoved....
    Durch die rvalue-Referenzen am T3 wird jetzt auch weniger gemoved...



  • Mein neuer Favorit ist nun eine index-basierte Variante inspiriert durch camper:
    http://www.c-plusplus.net/forum/p2110373#2110373

    // © camper
    template <std::size_t...> struct indexes {}; 
    template <typename> struct index_add; 
    template <std::size_t... i> struct index_add<indexes<i...>> { typedef indexes<0, ( i + 1 )...> type; }; 
    template <std::size_t N> struct make_indexes { typedef typename index_add<typename make_indexes<N-1>::type>::type type; }; 
    template <> struct make_indexes<0> { typedef indexes<> type; };
    
    template <typename T> struct all_indexes { typedef typename make_indexes<std::tuple_size<T>::value >::type type; }; 
    
    template<std::size_t Index, typename T1, typename T2>
    typename std::tuple_element<Index, T1>::type divide_single_entry(T1& t1, T2& t2){
    	typedef typename std::tuple_element<Index, T1>::type result_type;
    	return static_cast<result_type>(std::get<Index>(t1)/std::get<Index>(t2));
    }
    
    template<std::size_t... Index, typename T1, typename T2>
    auto divide_all(indexes<Index...>, T1& t1, T2& t2) ->decltype(std::make_tuple(divide_single_entry<Index>(t1,t2)...)){
    	return std::make_tuple(divide_single_entry<Index>(t1,t2)...);
    }
    
    template<typename T1, typename T2>
    auto divide(T1& t1, T2& t2) -> decltype(divide_all(typename all_indexes<T1>::type(),t1, t2)){
    	return divide_all(typename all_indexes<T1>::type(),t1, t2);
    }
    
    int main(){ 
        typedef std::tuple<float, int, int> tuple_type1; 
        tuple_type1 t1(1.2, 12, 5); 
        typedef std::tuple<int, double, int> tuple_type2; 
        tuple_type2 t2(2, 2.1, 2);
    	tuple_type1 res(divide(t1, t2));
    	std::cout << std::get<0>(res) << ' ' << std::get<1>(res) << ' ' << std::get<2>(res) << std::endl; 
    }
    

  • Mod

    Das geht noch etwas einfacher:

    #include <tuple>
    
    template <typename T> struct identity { typedef T type; };
    template <std::size_t... i> struct indexes
        : identity<indexes<i...>> {};
    template <typename... T> struct index_cat
        : index_cat<typename T::type...> {};
    template <std::size_t... i0, std::size_t... i1, typename... T> struct index_cat<indexes<i0...>, indexes<i1...>, T...>
        : index_cat<indexes<i0..., ( i1 + sizeof... i0 )...>, T...> {};
    template <typename T> struct index_cat<T>
        : T {};
    template <typename T> struct index_twice
        : index_cat<T, T> {};
    template <std::size_t N> struct index_make
        : index_cat<index_twice<index_make<N/2>>, index_make<N%2>> {};
    template <> struct index_make<1>
        : indexes<0> {};
    template <> struct index_make<0>
        : indexes<> {};
    template <std::size_t N>
    constexpr typename index_make<N>::type make_indexes() { return typename index_make<N>::type(); }
    ///////////////////////////////////////////////////////////////////////////////////////////////
    
    template <typename T, typename U, std::size_t... i>
    constexpr typename std::decay<T>::type divide(T&& t, U&& u, indexes<i...>)
    {
        return typename std::decay<T>::type( ( std::get<i>( t ) / std::get<i>( u ) )... );          // alle Packs werden gleichzeitig expandiert
    }
    
    template<typename  T, typename U>
    constexpr typename std::decay<T>::type divide(T&& t, U&& u)
    {
        return divide( std::forward<T>( t ), std::forward<U>( u ), make_indexes<std::tuple_size<typename std::decay<T>::type>::value>() );
    }
    
    #include <iostream>
    int main(){
        typedef std::tuple<float, int, int> tuple_type1;
        tuple_type1 t1(1.2, 12, 5);
        typedef std::tuple<int, double, int> tuple_type2;
        tuple_type2 t2(2, 2.1, 2);
        tuple_type1 res(divide(t1, t2));
        std::cout << std::get<0>(res) << ' ' << std::get<1>(res) << ' ' << std::get<2>(res) << std::endl;
    }
    


  • Kannst du mir evtl. noch erläutern, wann man std::decay benutzt?
    Also msdn macht mich irgendwie auch nicht schlau 😞
    http://msdn.microsoft.com/de-de/library/ee361638.aspx



  • Verwendet man, wenn man Referenzen entfernen möchte, um Elemente kopieren zu können, oder top-level const/volatile zu entfernen, weil man eben sowieso kopiert, wenn ich das richtig verstanden haben.


  • Mod

    decay<T>::type ist der Typ, in den T in value-Übergaben zerfällt.
    Also für

    template <typename U> void foo(T);
    

    der deduzierte Typ U für einen Ausdruck der Form

    foo(expression-of-type-T)
    

    In diesem speziellen Fall wollte ich nur die Referenz loswerden, also hätte es auch remove_reference getan. Allerdings ist decay kürzer und möglicherweise sogar aussagekräftiger.


Log in to reply