Search code examples
c++c++14stdtuple

Insert/get tuple into/from vector of tuples


I'm trying to insert/get a tuple into/from a vector of tuples and came up with the following code snippet. It works fine, but I'm not entirely happy with it. Can anyone think of a more elegant solution? Is it possible to generalize the 'for_each_in_tuple_and_arg' function into a 'for_each_in_tuples' function? PS: I'm stuck with a C++14 compiler...

#include <tuple>
#include <vector>
#include <utility>

template <std::size_t I = 0, class Fn, class Tuple>
constexpr typename std::enable_if
    <I == std::tuple_size<Tuple>::value, void>::type
for_each_in_tuple(Fn&&, Tuple&) {}

template <std::size_t I = 0, class Fn, class Tuple>
constexpr typename std::enable_if
    <I != std::tuple_size<Tuple>::value, void>::type
for_each_in_tuple(Fn&& fn, Tuple& tup)
{
    fn(std::get<I>(tup));
    for_each_in_tuple<I + 1>(fn, tup);
}

template <std::size_t I = 0, class Fn, class Tuple, class Arg>
constexpr typename std::enable_if
    <I == std::tuple_size<Tuple>::value, void>::type
for_each_in_tuple_and_arg(Fn&&, Tuple&, const Arg&) {}

template <std::size_t I = 0, class Fn, class Tuple, class Arg>
constexpr typename std::enable_if
    <I != std::tuple_size<Tuple>::value, void>::type
for_each_in_tuple_and_arg(Fn&& fn, Tuple& tup, const Arg& arg)
{
    fn(std::get<I>(tup), std::get<I>(arg));
    for_each_in_tuple_and_arg<I + 1>(fn, tup, arg);
}

class tuple_of_vectors
{
public:
    using tov_type = std::tuple<
        std::vector<int>, std::vector<int>, std::vector<int>>;

    using value_type = std::tuple<int, int, int>;

    void reserve(std::size_t n)
    {
        auto fn = [n](auto& vec){ vec.reserve(n); };
        for_each_in_tuple(fn, tov_);
    }

    value_type at(std::size_t n) const noexcept
    {
        value_type res{};
        auto fn = [n](auto& val, const auto& vec)
            { val = vec.at(n); };
        for_each_in_tuple_and_arg(fn, res, tov_);
        return res;
    }

    void insert(const value_type& tup)
    {
        std::size_t n = 0;
        auto fn = [n](auto& vec, const auto& val)
            { vec.insert(vec.cbegin() + n, val); };
        for_each_in_tuple_and_arg(fn, tov_, tup);
    }

    tov_type tov_;
};

Solution

  • As it is quite easy to write such things in current C++, which can be something like:

    template <typename TUPPLE_T, typename FUNC, typename ... ARGS >
    void for_each_in_tuple( TUPPLE_T& tu, FUNC fn, ARGS... args )
    {
        std::apply( [&args..., fn]( auto& ... n) { (fn(args..., n),...); }, tu);
    } 
    
    int main()
    {
        std::tuple< int, int> tu{1,2};
        auto l_incr = [](int& i){i++;};
        auto l_add = []( int val, int& i){i+=val; };
    
        for_each_in_tuple( tu, l_incr );
        std::cout << std::get<0>(tu) << ":" << std::get<1>(tu) << std::endl;
    
        for_each_in_tuple( tu, l_add, 5 );
        std::cout << std::get<0>(tu) << ":" << std::get<1>(tu) << std::endl;
    }
    

    C++20 on godbolt

    but you are fixed at C++14. OK, so lets copy and minimize the stuff from the STL as needed to get it work with an "handcrafted" apply functionality like:

    namespace detail {
    template <class F, class Tuple, std::size_t... I>
    constexpr decltype(auto) apply_impl(F&& f, Tuple&& t, std::index_sequence<I...>)
    {
        return f(std::get<I>(std::forward<Tuple>(t))...);
    }
    }  // namespace detail
    
    template <class F, class Tuple>
    constexpr decltype(auto) apply(F&& f, Tuple&& t)
    {
        return detail::apply_impl(
            std::forward<F>(f), std::forward<Tuple>(t),
            std::make_index_sequence<std::tuple_size<std::remove_reference_t<Tuple>>::value>{});
    }
    
    // Workaround for missing fold expression in C++14. Simply use
    // list initializer to get function called. It guarantees also
    // execution order... 
    struct EatEverything
    {
        template < typename ... T>
            EatEverything(T...){}
    };
    
    template <typename TUPPLE_T, typename FUNC, typename ... ARGS >
    void for_each_in_tuple( TUPPLE_T& tu, FUNC fn, ARGS... args )
    {
        apply( [&args..., fn]( auto& ... n) { EatEverything{ (fn(args..., n),0)... };}, tu);
    }
    
    int main()
    {
        std::tuple< int, int> tu{1,2};
        auto l_incr = [](int& i){i++;};
        auto l_add = []( int val, int& i){i+=val; };
    
        for_each_in_tuple( tu, l_incr );
        std::cout << std::get<0>(tu) << ":" << std::get<1>(tu) << std::endl;
    
        for_each_in_tuple( tu, l_add, 5 );
        std::cout << std::get<0>(tu) << ":" << std::get<1>(tu) << std::endl;
    }
    

    C++14 on godbolt

    As we only have written a C++14 apply function, we can remove that stuff later while a non outdated compiler can be used and we stay with the single line of code implementation.

    Hint: I know that we can do all the stuff much better by using forwarding refs and std::forward and so on... it is an example and wants to show how we can use apply instead of handcrafted reverse algorithm.