Functorproxy für boost::tuple mit Metaprogrammierung?



  • Schönen Abend! 🙂

    Ich habe mich in Java-Streams verliebt und wollte das Ganze mal zum Spaß in C++ nachbilden bzw. für meine Bedürfnisse besser gestalten. Das Ganze lässt sich prinzipiell ja sehr gut mit Iteratoren-Paaren machen, insbesondere mit den Iteratoren, die es in Boost.Iterator so gibt.

    In Java fehlt mir besonders ein Zip-Stream, also die Möglichkeit über zwei Streams gleichzeitig zu laufen. Das habe ich mal brauchbar implementiert:

    #include <iostream>
    #include <vector>
    #include <list>
    #include <cstring>
    #include <set>
    
    #include <boost/iterator/filter_iterator.hpp>
    #include <boost/iterator/transform_iterator.hpp>
    #include <boost/iterator/zip_iterator.hpp>
    #include <boost/tuple/tuple.hpp>
    #include <algorithm>
    #include <memory>
    
    template<typename IterT>
    class iterator_stream {
    private:
    	IterT begin_, end_;
    
    public:
    	iterator_stream(IterT begin, IterT end) : begin_(std::move(begin)), end_(std::move(end)) { }
    
    	IterT begin() const {
    		return begin_;
    	}
    
    	IterT end() const {
    		return end_;
    	}
    
    	template<typename CallbackT> void
    	for_each(CallbackT cb) {
    		std::for_each(begin_, end_, cb);
    	}
    
    	template<typename PredicateT>
    	iterator_stream<boost::filter_iterator<PredicateT, IterT>> filter(PredicateT predicate) {
    		typedef boost::filter_iterator<PredicateT, IterT> FilterIterT;
    		return iterator_stream<FilterIterT>(FilterIterT(predicate, begin_, end_), FilterIterT(predicate, end_, end_));
    	}
    
    	template<typename MapperT>
    	iterator_stream<boost::transform_iterator<MapperT, IterT>> map(MapperT mapper) {
    		typedef boost::transform_iterator<MapperT, IterT> MapIterT;
    		return iterator_stream<MapIterT>(MapIterT(begin_, mapper), MapIterT(end_, mapper));
    	}
    
    };
    
    template<typename ContainerT>
    auto stream(ContainerT& c) -> iterator_stream<typename ContainerT::iterator> {
    	return iterator_stream<typename ContainerT::iterator>(c.begin(), c.end());
    }
    
    template<typename T, std::size_t Size>
    iterator_stream<T*> stream(T (&a)[Size]) {
    	return iterator_stream<T*>(a, a + Size);
    }
    
    template<typename Iter1T, typename Iter2T>
    iterator_stream<boost::zip_iterator<boost::tuple<Iter1T, Iter2T>>> zip(iterator_stream<Iter1T> const& s1, iterator_stream<Iter2T> const& s2) {
    	typedef boost::zip_iterator<boost::tuple<Iter1T, Iter2T>> ZipIterT;
    	return iterator_stream<ZipIterT>(
    		ZipIterT(boost::make_tuple(s1.begin(), s2.begin())),
    		ZipIterT(boost::make_tuple(s1.end(), s2.end())));
    }
    
    int main() {
    	std::vector<char const*> vec = { "Hello", "World", "!"};
    	stream(vec)
    		.filter([](auto& str) { return std::strlen(str) > 1; })
    		.map([](auto& str) { return std::strlen(str); })
    		.for_each([](auto len) { std::cout << len << std::endl; });
    
    	std::list<std::string> en = { "Hello", "World", "Asshole" };
    	char const* de[] = { "Hallo", "Welt", "Arschloch" };
    	std::set<std::string> insults = { "Asshole" };
    	zip(stream(en), stream(de))
    		.filter([&] (auto const& t) { return insults.find(boost::get<0>(t)) == insults.end(); })
    		.map([](auto const& t) { return boost::get<0>(t) + " - " + boost::get<1>(t); })
    		.for_each([] (auto const& str) { std::cout << str << std::endl; });
    }
    

    Bzw. hier auf Ideone zum ausprobieren: http://ideone.com/HSZq2F

    Dann komme ich gleich zu meinem Problem, das sich aufgrund meiner schwer eingerosteten C++ Kenntnisse für mich nicht vernünftig lösen lässt:

    [](auto const& t) { return boost::get<0>(t) + " - " + boost::get<1>(t);
    

    Das finde ich hässlich. boost::tuple ist für mich ein Implementierungsdetail, mit dem man sich nicht rumschlagen sollte. Die Lesbarkeit leidet auch darunter.

    [](auto const& word1, auto const& word2) { return word1 + " - " + word2;
    

    ist, was ich mir wünschen würde. Es bräuchte also einen Proxy-Funktor, der variadisch das Tuple entpackt und mit dem Inhalt das Lambda (also den Callback) aufruft.

    Kann mir bitte jemand auf die Sprünge helfen?
    Danke und schöne Grüße,
    Ethon


  • Mod

    Das entpacken von Tuples ist doch schon oft auf SO demonstriert worden. Du generierst ad hoc eine index_sequence und rufst darüber get auf.

    template <typename Tup, typename F, typename... Is>
    constexpr decltype(auto) expand(Tup&& t, F f, std::index_sequence<Is...>) {
        return f(std::get<Is>(std::forward<Tup>(t))...);
    }
    
    template <typename Tup, typename F>
    constexpr decltype(auto) expand(Tup&& t, F f) {
        return expand(std::forward<T>(t), f, std::make_index_sequence<std::tuple_length<Tup>{}>{});
    }
    

    (Ungetestet)

    Das wird übrigens mit ad hoc packs deutlich einfacher. Ich arbeite gerade an einem entsprechenden Paper.



  • War etwas umzubauen (boost::tuple funktioniert auch anders als std::tuple) aber vielen Dank, funktioniert jetzt. 👍



  • Schon mal std::apply(func, tupleObject) probiert? apply() wendet func auf alle Tupelelemente an. Ab c++14, soweit ich weiß


Anmelden zum Antworten