patterncppMinor
for_each for tuple-likes
Viewed 0 times
tupleforfor_eachlikes
Problem
I've implemented a C++14
With a visitor functor:
With a C++14 generic lambda:
With a stateful functor:
With a
Implementation:
``
template
struct for_each_t {
constexpr UnaryFunction&& operator()(Tuple&& t, UnaryFunction&& f) const
{
std::forward(f)(
std::get(std::forward(t)));
return for_each_t,
ToIndex,
Tuple,
UnaryFunction>()(
std::forward(t), std::forward(f));
}
};
// specialization for empty tuple-likes
template
struct for_each_t, Tuple, UnaryFunction> {
constexpr UnaryFunction&& operator()(Tuple&&, UnaryFunction&& f) const
{
return std::fo
for_each for tuple-like objects. It's similar to std::for_each in that it also returns the functor once it's done. Usage examples:With a visitor functor:
// visitor functor
struct print {
void operator()(int x) const { std::cout << "int: " << x << '\n'; }
void operator()(double x) const { std::cout << "double: " << x << '\n'; }
};
auto t = std::make_tuple(1, 2, 3.14);
for_each(t, print()); // prints: int: 1
// int: 2
// double: 3.14With a C++14 generic lambda:
auto t = std::make_tuple(1, 2, 3.14);
for_each(t, [](auto x) { std::cout << x << '\n'; }); // prints: 1
// 2
// 3.14With a stateful functor:
struct summer {
void operator()(int x) noexcept { sum += x; }
int sum = 0;
};
auto t = std::make_tuple(1, 2, 3, 4, 5);
int sum = for_each(t, summer()).sum; // sum == 15With a
std::array:std::array arr = {{'h', 'e', 'l', 'l', 'o'}};
for_each(arr, [](char c) { std::cout << c; }); // prints: hello
std::cout << '\n';Implementation:
``
#include
#include
#include
namespace detail {
// workaround for default non-type template arguments
template
using index_t = std::integral_constant;
// process the From::value`-th elementtemplate
struct for_each_t {
constexpr UnaryFunction&& operator()(Tuple&& t, UnaryFunction&& f) const
{
std::forward(f)(
std::get(std::forward(t)));
return for_each_t,
ToIndex,
Tuple,
UnaryFunction>()(
std::forward(t), std::forward(f));
}
};
// specialization for empty tuple-likes
template
struct for_each_t, Tuple, UnaryFunction> {
constexpr UnaryFunction&& operator()(Tuple&&, UnaryFunction&& f) const
{
return std::fo
Solution
I'm going to start with your concern #3:
I've seen people do it with
in this
post
is the shortest version I've seen, but it feels a bit like a hack.
Also, it generates longer assembly
code than my
version does. (My version generates the
exact same assembly code as a completely manually expanded
version.)
The placement of a single
Concern #2:
Whether the design can be simplified.
At this point, there isn't more that I can say other than Louis Dionne's answer is much simpler than your solution:
Fixed by adding the two missing
Compared to your code, this is much easier to understand, largely because there is much less code, as well as the fact that this style of doing something for each element of a parameter pack is pretty standard nowadays.
I know you are concerned that this feels like a hack, but I assure you, it is not really. It's the easiest way to regain a parameter pack from a tuple-like type.
Additionally, doing work with parameter packs tends to be more efficient than recursive functions (in terms of compile-time). That is not something to disregard casually.
Furthermore, compilers have a pretty small limit for
I've seen people do it with
std::index_sequence. The accepted answerin this
post
is the shortest version I've seen, but it feels a bit like a hack.
Also, it generates longer assembly
code than my
version does. (My version generates the
exact same assembly code as a completely manually expanded
version.)
The placement of a single
constexpr in the index-sequence version makes the generated assembly identical for both versions. The missing constexpr was on the for_each_impl that was doing most of the work in the index-sequence versionConcern #2:
Whether the design can be simplified.
At this point, there isn't more that I can say other than Louis Dionne's answer is much simpler than your solution:
#include
#include
#include
template
void for_each_impl(Tuple&& tuple, F&& f, std::index_sequence) {
using swallow = int[];
(void)swallow{1,
(f(std::get(std::forward(tuple))), void(), int{})...
};
}
template
void for_each(Tuple&& tuple, F&& f) {
constexpr std::size_t N = std::tuple_size>::value;
for_each_impl(std::forward(tuple), std::forward(f),
std::make_index_sequence{});
}Fixed by adding the two missing
constexprs (one each function):#include
#include
#include
template
constexpr void for_each_impl(Tuple&& tuple, F&& f, std::index_sequence) {
using swallow = int[];
(void)swallow{1,
(f(std::get(std::forward(tuple))), void(), int{})...
};
}
template
constexpr void for_each(Tuple&& tuple, F&& f) {
constexpr std::size_t N = std::tuple_size>::value;
for_each_impl(std::forward(tuple), std::forward(f),
std::make_index_sequence{});
}Compared to your code, this is much easier to understand, largely because there is much less code, as well as the fact that this style of doing something for each element of a parameter pack is pretty standard nowadays.
I know you are concerned that this feels like a hack, but I assure you, it is not really. It's the easiest way to regain a parameter pack from a tuple-like type.
Additionally, doing work with parameter packs tends to be more efficient than recursive functions (in terms of compile-time). That is not something to disregard casually.
Furthermore, compilers have a pretty small limit for
constexpr recursion compared to runtime recursion. On my version of gcc, it was 500. Yes, that's not a trivially small amount and it would work for most tuples you pass to your for_each, but different compilers could choose different allowed recursion depths. Also, it's not inconceivable that I would want to have a std::array, which would simply break with your version (you would probably want std::for_each in this case, though).Code Snippets
#include <cstddef>
#include <tuple>
#include <utility>
template <typename Tuple, typename F, std::size_t ...Indices>
void for_each_impl(Tuple&& tuple, F&& f, std::index_sequence<Indices...>) {
using swallow = int[];
(void)swallow{1,
(f(std::get<Indices>(std::forward<Tuple>(tuple))), void(), int{})...
};
}
template <typename Tuple, typename F>
void for_each(Tuple&& tuple, F&& f) {
constexpr std::size_t N = std::tuple_size<std::remove_reference_t<Tuple>>::value;
for_each_impl(std::forward<Tuple>(tuple), std::forward<F>(f),
std::make_index_sequence<N>{});
}#include <cstddef>
#include <tuple>
#include <utility>
template <typename Tuple, typename F, std::size_t ...Indices>
constexpr void for_each_impl(Tuple&& tuple, F&& f, std::index_sequence<Indices...>) {
using swallow = int[];
(void)swallow{1,
(f(std::get<Indices>(std::forward<Tuple>(tuple))), void(), int{})...
};
}
template <typename Tuple, typename F>
constexpr void for_each(Tuple&& tuple, F&& f) {
constexpr std::size_t N = std::tuple_size<std::remove_reference_t<Tuple>>::value;
for_each_impl(std::forward<Tuple>(tuple), std::forward<F>(f),
std::make_index_sequence<N>{});
}Context
StackExchange Code Review Q#134814, answer score: 4
Revisions (0)
No revisions yet.