17
\$\begingroup\$

I wrote a "foreach" implementation for std::tuple:

#pragma once

#include <tuple>
/**
 * Callback example:

struct Call{
    float k=0;

    template<typename T, int Index>        // lambda function not efficient than this. Tested -O2 clang, gcc 4.8
    inline void call(T &&t){
        std::cout << t.h << " ; " << "id = " << Index << std::endl;
    }
};
*/


namespace TUPLE_ITERATOR{
    template<typename Tuple, int index, int size>
    struct LOOP{
        template <typename Callback>
        static inline void wind(Tuple&& tuple, Callback&& callback){
            callback.template call<decltype(std::get<index>(tuple)), index> (std::get<index>(tuple));
            LOOP<Tuple, index+1, size>::wind( std::forward<Tuple>(tuple), std::forward<Callback>(callback) );
        }
    };

    template<typename Tuple, int size>
    struct LOOP_BACK{
        template <typename Callback>
        static inline void wind_reverse(Tuple&& tuple, Callback&& callback){
            callback.template call<decltype(std::get<size>(tuple)), size>( std::get<size>(tuple) );
            LOOP_BACK<Tuple, size-1>::wind_reverse( std::forward<Tuple>(tuple), std::forward<Callback>(callback) );
        }
    };

    // stop specialization
    template<typename Tuple, int size>
    struct LOOP<Tuple, size, size> {
        template <typename Callback>
        static inline void wind(Tuple&& , Callback&& ){
            // end
        }
    };
    template<typename Tuple>
    struct LOOP_BACK<Tuple, -1>{
        template <typename Callback>
        static inline void wind_reverse(Tuple&& , Callback&& ){
            // end
        }
    };
}

template<typename Tuple, typename Callback>
static void inline iterate_tuple(Tuple&& tuple, Callback&& callback){
    TUPLE_ITERATOR::LOOP< Tuple, 0, std::tuple_size< typename std::decay<Tuple>::type >::value >
            ::template wind<Callback>( std::forward<Tuple>(tuple), std::forward<Callback>(callback) );
}

template<typename Tuple, typename Callback>
static void inline iterate_tuple_back(Tuple&& tuple, Callback&& callback){
    TUPLE_ITERATOR::LOOP_BACK< Tuple, std::tuple_size< typename std::decay<Tuple>::type >::value-1 >
            ::template wind_reverse<Callback>( std::forward<Tuple>(tuple), std::forward<Callback>(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 red.

void TUPLE_ITERATOR::LOOP<std::tuple<std::pair<int, Data>,
std::pair<int, Data> >, 0, 2>:
\$\endgroup\$
  • \$\begingroup\$ I used to use recursion a lot with templates. But I have moved from recursion to using std::integer_sequence and std::tuple to get the equivalent of a loop (though underneath in the standard code it is still recursion it is not visible from my code and thus easier to read). \$\endgroup\$ – Martin York May 22 '14 at 19:33
  • \$\begingroup\$ So, does your implementation suffers from such "issue", like mine? \$\endgroup\$ – tower120 May 22 '14 at 19:37
  • \$\begingroup\$ If you're able to move to C++17, you could probably replace all this with one fold-expression. \$\endgroup\$ – Toby Speight Jul 13 '18 at 12:03
24
\$\begingroup\$

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:

#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>{});
}

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 <iostream>

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<Indices>(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<Indices>(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<Indices>(tuple)), void(), int{})...};
                                      ^~~~ Make sure the builtin operator, is used

Then, 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<Indices>(tuple)), void(), int{})...};
        ^~~~ Now the array always has at least one element in it

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 swallow{...} to void. Finally, just add perfect forwarding of the Tuple and you're done:

(void)swallow{1, (f(std::get<Indices>(std::forward<Tuple>(tuple))), void(), int{})...};
^^^^^^ Silence warning                ^^^^^^^^^^^^^^^^^^^ Perfect forwarding     

Note 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<Indices>(std::forward<Tuple>(tuple)))...};

Now, tuple might be moved-from several times:

swallow{
    f(function_that_moves_from_its_arg<Index1>(std::forward<Tuple>(tuple))), // move here
    f(function_that_moves_from_its_arg<Index2>(std::forward<Tuple>(tuple))), // move here
    f(function_that_moves_from_its_arg<Index3>(std::forward<Tuple>(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.

\$\endgroup\$
  • \$\begingroup\$ Really nice solution. To be honest I don't really understand how you call target function with swallow. And I can't make it work coliru.stacked-crooked.com/a/98be1303f2670c8b \$\endgroup\$ – tower120 Oct 22 '14 at 5:25
  • \$\begingroup\$ I edited my answer so it works on both Clang and GCC, and explained it better. Look at the edit above. \$\endgroup\$ – Louis Dionne Oct 22 '14 at 15:17
  • \$\begingroup\$ 'To silence it, I cast the swallow{...} to void' - It still may be truncated by compiler, I think... As you actually not use that .... void? \$\endgroup\$ – tower120 Oct 24 '14 at 6:40
  • \$\begingroup\$ I've never heard of a compiler getting rid of an expression of type void (with side effects) because its result is never used (which is always the case, because void can never be used). I'm also pretty sure the language does not allow a compiler to do so, but I don't have the time to get a quote from the standard right now. \$\endgroup\$ – Louis Dionne Oct 24 '14 at 13:46
  • \$\begingroup\$ So what exactly is this "void" here? Array declaration, function declaration? [I understand that when this array instantiated, it evaluates all elements inside it (and so call f).] I just don't really understand what happens when cast array to void... \$\endgroup\$ – tower120 Oct 25 '14 at 4:54
7
\$\begingroup\$

I used to use recursion a lot with templates. But I have moved from recursion to using std::integer_sequence and std::tuple to get the equivalent of a loop (from posting my template code here).

Though underneath in the standard code it is still recursion it is not visible from my code and thus easier to read.

I am not trying to implement exactly what you have.
But if you look at the code you can see what I am trying to achiece and may be able to apply this technique to your code (thus making it easier to read and thus maintain).

#include <tuple>
#include <iostream>
#include <utility>



// The object that defines the iteration.
// Notice the use of make_integer_sequence here (it returns integer_sequence type)
//
// This defines how we are going to iterate over the tuple T.
//    
template<typename C, typename T, typename Seq = std::make_integer_sequence<int, std::tuple_size<T>::value>>
struct TupleIterate;


// A partial specialization of the above.
// Here we convert the integer_sequence into a sequence of integers S
// We can use variable argument expansion to generate the code inline
// with this sequence.
//
template<typename C, typename T, int... S>
struct TupleIterate<C, T, std::integer_sequence<int, S...>>
{
    TupleIterate(C caller, T const& val)
    {
        // Make a tuple.
        // This takes a variable number of arguments and creates the appropriate
        // tuple. As we don't use the tuple we don't even bother to store it.
        //
        // Use Var-Arg expansion to call caller for each argument in T
        // The results of these called are passed to make_tuple()
        //
        std::make_tuple(caller(std::get<S>(val))...);
    }
};

//
// Function: To allow argument deduction
//           Pass the values as parameters and it creates the TupleIterator
//           defined above. Because it deduces the types of its arguments
//           we don't need to specify them.
template<typename C, typename T>
TupleIterate<C,T> tuple_iterate(C caller, T const& val)
{
    return TupleIterate<C,T>(caller, val);
}


// An example caller object.
// Just to show it printing.
struct Caller
{
    // It needs an operator() for each type in the tuple.
    // For ease of use I have templatized this.
    template<typename T>
    T operator()(T const& data)
    {
        std::cout << "Data: " << data << "\n";
        return data;
    }
};

int main()
{
    auto    val = std::make_tuple(1,2,"Hi there");

    tuple_iterate(Caller(), val);
}
\$\endgroup\$
  • \$\begingroup\$ It does not compile goo.gl/wGaNJ8 \$\endgroup\$ – tower120 May 22 '14 at 19:47
  • \$\begingroup\$ @tower120 std::integer_sequence is a C++14 feature, you will need to give the option -std=c++1y to your compiler. Also, only the most recent compiler (often trunk versions) implement it. Choose GCC 4.9. \$\endgroup\$ – Morwenn May 22 '14 at 19:49
  • \$\begingroup\$ On the other hand, you can find several standard-compliant implementations on the internet that you can copy-paste meanwhile :) \$\endgroup\$ – Morwenn May 22 '14 at 19:49
  • \$\begingroup\$ g++ -std=c++1y call.cpp \$\endgroup\$ – Martin York May 22 '14 at 19:50
  • \$\begingroup\$ @LokiAstari Look here goo.gl/Eixw1v. At the bottom of assembly output 'TupleIterate<Caller, std::tuple<int, int, char const*>, ' Instead of proceed to put std::cout call's, compiler replace them with 2 TupleIterate calls. I mean, now its not plain list of std::cout calls (like it was with single tuple_iterate call). This is that issue, which have my own implementation. \$\endgroup\$ – tower120 May 22 '14 at 19:56
2
\$\begingroup\$

If you're exclusively interested in for_each, the Morwenn/Martin York response is pretty neat. Otherwise, a meaningful generalisation is the use of std::tuple with STL algorithms. To that end, your good bet is Jonathan Müller's tuple_iterator. That would give you iterator access to std::tuple, which you would also be able to use for std::for_each, in particular.

\$\endgroup\$
  • \$\begingroup\$ Unfortunately, tuple_iterator is not as generalised as I initially thought. One cannot pass a couple of them to std::for_each with a member function to be called on each element in the tuple. I couldn't figure out a way to deduce the element's type from the iterator to guide the member function. \$\endgroup\$ – Hossein Jul 22 '18 at 17:23
  • \$\begingroup\$ I need to correct myself on the last comment: My failure was due to my (old-school) trial for using std::mem_fun. I managed to make the member function call I was after using generic lambdas: auto t = my_tuple(); std::for_each(begin(t), end(t), [](auto& e){e.mf();}); where mf is the name of the member function I'd like to call. In my case, my mf has important side-effects and I pass by non-const reference. YMMV. \$\endgroup\$ – Hossein Jul 23 '18 at 14:41
0
\$\begingroup\$

Building on top of Louisse Dionne's solution and simplifying for_each slightly by using index_sequence_for we could arrive at this:

#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 F, typename... Args>
void for_each(const std::tuple<Args...>& tuple, F&& f)
{
    for_each_impl(tuple, std::forward<F>(f), std::index_sequence_for<Args...>{});
}
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.