Christoph Schreiber
Christoph Schreiber

Reputation: 23

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_;
};

Upvotes: 0

Views: 286

Answers (2)

Klaus
Klaus

Reputation: 25613

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.

Upvotes: 2

fabian
fabian

Reputation: 82461

Simply add a varidic template parameter:

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

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

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(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(fn, tov_, tup);
    }

    tov_type tov_;
};

Upvotes: 1

Related Questions