Insert/get 元组 into/from 元组向量

Insert/get tuple into/from vector of tuples

我正在尝试 insert/get 一个元组 into/from 一个元组向量,并提出了以下代码片段。它工作正常,但我对它并不完全满意。 谁能想到一个更优雅的解决方案?是否可以将 'for_each_in_tuple_and_arg' 函数泛化为 'for_each_in_tuples' 函数? PS:我受困于 C++14 编译器...

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

只需添加可变模板参数:

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

因为在当前的 C++ 中很容易写出这样的东西,可以是这样的:

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

但你固定在 C++14。好的,让我们根据需要从 STL 中复制和最小化内容,以使其与“手工制作”的应用功能一起使用,例如:

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

因为我们只写了一个 C++14 apply 函数,我们可以稍后删除那些东西,同时可以使用非过时的编译器,我们保持单行代码实现。

提示:我知道我们可以通过使用转发 refs 和 std::forward 等来更好地完成所有事情......这是一个例子,想展示我们如何使用 apply 而不是 handcrafted反向算法。