Reputation: 23
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
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;
}
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;
}
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
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