patterncppMajor
std::tuple foreach implementation
Viewed 0 times
stdforeachtupleimplementation
Problem
I wrote a "foreach" implementation for
But I look at how other folks do that, and I see that they do this in another way. They get an array of indices, and then recursively call the
std::tuple:#pragma once
#include
/**
* Callback example:
struct Call{
float k=0;
template // lambda function not efficient than this. Tested -O2 clang, gcc 4.8
inline void call(T &&t){
std::cout
struct LOOP{
template
static inline void wind(Tuple&& tuple, Callback&& callback){
callback.template call(tuple)), index> (std::get(tuple));
LOOP::wind( std::forward(tuple), std::forward(callback) );
}
};
template
struct LOOP_BACK{
template
static inline void wind_reverse(Tuple&& tuple, Callback&& callback){
callback.template call(tuple)), size>( std::get(tuple) );
LOOP_BACK::wind_reverse( std::forward(tuple), std::forward(callback) );
}
};
// stop specialization
template
struct LOOP {
template
static inline void wind(Tuple&& , Callback&& ){
// end
}
};
template
struct LOOP_BACK{
template
static inline void wind_reverse(Tuple&& , Callback&& ){
// end
}
};
}
template
static void inline iterate_tuple(Tuple&& tuple, Callback&& callback){
TUPLE_ITERATOR::LOOP::type >::value >
::template wind( std::forward(tuple), std::forward(callback) );
}
template
static void inline iterate_tuple_back(Tuple&& tuple, Callback&& callback){
TUPLE_ITERATOR::LOOP_BACK::type >::value-1 >
::template wind_reverse( std::forward(tuple), std::forward(callback) );
}
// Call:
// iterate_tuple(Callback(), std::make_tuple(1,2,3,"asdaa"));But I look at how other folks do that, and I see that they do this in another way. They get an array of indices, and then recursively call the
callback function. Is my implementation worse than that? I ask this because if I call tuple_iterator twice, with the same parameters, the compiler starts to use asm "calls". Look HERE, on the rSolution
Loki's solution does not enforce the order in which the function calls are performed, because the order in which function arguments are evaluated is unspecified. Here's a C++14 solution that ensures the function is called from left to right:
I use
EDIT
I modified the code so it works on both GCC and Clang. Here's a more in-depth explanation of
First, we make sure that we call
But then, what if
The expression
Then, what happens if
We're almost done, but now there's an anoying compiler warning saying "You're creating a temporary array 'swallow' which is never used". To silence it, I cast the
Note that the way I use
Now,
However, I know
#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{});
}I use
swallow{f(x)...} to force the evaluation order. It works because the order in which the arguments to a brace initializer are evaluated is the order in which they appear. You can then use it like:#include
int main() {
for_each(std::make_tuple(1, '2', 3.3), [](auto x) {
std::cout << x << std::endl;
});
}EDIT
I modified the code so it works on both GCC and Clang. Here's a more in-depth explanation of
for_each_impl.First, we make sure that we call
f inside a braced initializer, so the evaluation order is from left to right:using swallow = int[];
swallow{f(std::get(tuple))...};But then, what if
f does not return an integer value? What if it returns void for example? So we use the comma operator to make sure the expression is an integer which can be used inside the braced initializer:swallow{(f(std::get(tuple)), int{})...};The expression
(f(stuff), int{})... is a parameter pack expansion. It expands to (f(stuff_1), int{}), (f(stuff_2), int{}), ..., (f(stuff_n), int{}), so each expression is really an int, except that some side effect has been performed before. Then, to avoid nasty overloads of the comma operator by whatever is returned by f, we insert a void between f(...) and int{}. Since operator,(SomeType, void) can't be overloaded, this ensures that the builtin operator, is used, which is what we want. This might seem overkill, but we do this in highly generic code where we must assume that f(...) could overload operator,:swallow{(f(std::get(tuple)), void(), int{})...};
^~~~ Make sure the builtin operator, is usedThen, what happens if
for_each_impl is sent 0 arguments? We're gonna try to create a 0-sized array, so we must make sure the array always has at least one element in it. We use a dummy int for this:swallow{1, (f(std::get(tuple)), void(), int{})...};
^~~~ Now the array always has at least one element in itWe're almost done, but now there's an anoying compiler warning saying "You're creating a temporary array 'swallow' which is never used". To silence it, I cast the
swallow{...} to void. Finally, just add perfect forwarding of the Tuple and you're done:(void)swallow{1, (f(std::get(std::forward(tuple))), void(), int{})...};
^^^^^^ Silence warning ^^^^^^^^^^^^^^^^^^^ Perfect forwardingNote that the way I use
std::forward here could be unsafe in other circumstances. This is because tuple could be double-moved-from if the function I forwarded it to had different characteristics. Consider:swallow{f(function_that_moves_from_its_arg(std::forward(tuple)))...};Now,
tuple might be moved-from several times:swallow{
f(function_that_moves_from_its_arg(std::forward(tuple))), // move here
f(function_that_moves_from_its_arg(std::forward(tuple))), // move here
f(function_that_moves_from_its_arg(std::forward(tuple))), // move here
...
}However, I know
std::get is a friendly function and so there's should be no problem in doing this. There's an alternative way to do it "safely", but it involves using std::tuple_element and it's more complicated.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 <iostream>
int main() {
for_each(std::make_tuple(1, '2', 3.3), [](auto x) {
std::cout << x << std::endl;
});
}using swallow = int[];
swallow{f(std::get<Indices>(tuple))...};swallow{(f(std::get<Indices>(tuple)), int{})...};swallow{(f(std::get<Indices>(tuple)), void(), int{})...};
^~~~ Make sure the builtin operator, is usedContext
StackExchange Code Review Q#51407, answer score: 27
Revisions (0)
No revisions yet.