Search code examples
c++templatestuplesvariadic-functions

How to do recursion based on tuple elements?


I'm not sure if this is possible, but I would like to be able to recursively call a function based on the elements of a tuple. So for example a tuple such as std::tuple<int,float,double> should call expand_nested 3 times, and in turn invoke it's callback function with a parameter of type either int, float or double.

#include <tuple>
#include <vector>
#include <functional>

template <typename T>
struct tree_item
{
    T param;
    std::function<void(T)> callback;
};

template <typename... Ts>
struct tuple_node
{
    std::tuple<Ts...> tupl;
};

// recursion base case
template <typename T>
void expand_nested(tree_item<T> ti)
{
    ti.callback(ti.param);
}

// recursive function
template <typename T>
void expand_nested(tree_item<T> ti, tree_item<T> rest...)
{
    ti.callback(ti.param);
    expand_nested(ti, rest...);
}

template <typename... Ts>
void expand_root(tuple_node<Ts...> nodes)
{
    auto current = std::get<1>(nodes.tupl);
    auto rest = std::get<...>(nodes.tupl); // Made up syntax that doesn't work
    // How can I fill the "rest" variable with the remaining elements of the "nodes.tupl" tuple?

    expand_nested(current, rest...);
}

int main()
{
    tuple_node<tree_item<int>, tree_item<float>> nodes;

    tree_item<int> tree_int;
    tree_item<float> tree_float;
    tree_item<double> tree_double;

    tuple_node<tree_item<int>, tree_item<float>, tree_item<double>> node;
    node.tupl = std::make_tuple(tree_int, tree_float, tree_double);

    expand_root(nodes);
}


Solution

  • Syntax for parameters pack in expand_nested should be:

    template <typename T, typename ... Rest>
    void expand_nested(tree_item<T> ti, tree_item<Rest>... rest)
    

    This

    ti.callback(ti.param);
    expand_nested(ti, rest...);
    

    will give you infinite recursion (you are calling the same function with the same number of arguments of the first call), it should look like:

    template <typename T, typename ... Rest>
    void expand_nested(tree_item<T> ti, tree_item<Rest>... rest)
    {
        ti.callback(ti.param);    // process ti
        expand_nested(rest...);   // don't pass ti, pass only the rest to further processing
    }
    

    Since C++17 there is easy way to extract all elements of tuple - use std::apply:

    template <typename... Ts>
    void expand_root(tuple_node<Ts...> nodes)
    {
        std::apply([](auto&... tupleItems){ 
            expand_nested(tupleItems...); }, nodes.tupl);
    }
    

    Full demo