【问题标题】:How to do recursion based on tuple elements?如何基于元组元素进行递归?
【发布时间】:2020-06-20 18:04:58
【问题描述】:

我不确定这是否可行,但我希望能够基于元组的元素递归调用函数。例如,一个元组如std::tuple<int,float,double> 应该调用expand_nested 3 次,然后使用intfloatdouble 类型的参数调用它的回调函数。

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

【问题讨论】:

标签: c++ templates tuples variadic-functions


【解决方案1】:

expand_nested 中参数包的语法应该是:

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

这个

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

将给你无限递归(你正在调用相同的函数,第一次调用的参数数量相同),它应该看起来像:

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
}

从 C++17 开始,有一种简单的方法可以提取元组的所有元素 - 使用 std::apply:

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

Full demo

【讨论】:

    【解决方案2】:

    这在 C++20 中相当容易:

    #include <iostream>
    #include <tuple>
    
    
    template<typename T>
    void func(T t)
    {
        std::cout << t << std::endl;
    }
    
    int main()
    {
        std::tuple<int,float,double> f{1,2,3};
    
        std::apply([]<typename ...Args>(Args && ...args)
               {
                   (func(args), ...);
               }, f);
    }
    

    这需要更多的工作,仅支持 C++11 或更高版本:

    #include <iostream>
    #include <tuple>
    #include <utility>
    
    template<typename T>
    void func(T t)
    {
        std::cout << t << std::endl;
    }
    
    template<typename T, size_t s, size_t n=0>
    struct call_func {
    
        static void doit(T &t)
        {
            func(std::get<n>(t));
    
            call_func<T, s, n+1>::doit(t);
        }
    };
    
    template<typename T, size_t s>
    struct call_func<T, s, s> {
    
        static void doit(T &t)
        {
        }
    };
    
    template<typename ...Args>
    void do_callfunc(std::tuple<Args...> &t)
    {
        call_func<std::tuple<Args...>, sizeof...(Args), 0>::doit(t);
    }
    
    int main()
    {
        std::tuple<int,float,double> f{1,2,3};
    
        do_callfunc(f);
    }
    

    【讨论】:

    • 如果您只使用auto&amp;&amp;,第一个示例不能使用c++17吗?
    【解决方案3】:

    我不确定您是否需要扩展函数来做其他事情,但从 c++17 开始,您可以使用 std::apply 在单行中执行此操作。

    #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;
    };
    
    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);
    
        std::apply([](auto&&... args){
            (args.callback(args.param), ...);
        }, node.tupl);
    }
    

    【讨论】:

    • 这可能是对问题标题最直接的回答,但就我而言,我确实需要额外的步骤,即调用expand_nested
    【解决方案4】:

    您确定需要递归吗?

    如果你可以使用 C++17,那么模板折叠呢?

    template <typename... Ts, std::size_t ... Is>
    void expand_root_helper (tuple_node<Ts...> nodes, std::index_sequence<Is...>)
     { ((void)std::get<Is>(nodes.tupl).callback(
                 std::get<Is>(nodes.tupl).param), ...); }
    
    template <typename... Ts>
    void expand_root (tuple_node<Ts...> nodes)
     { expand_root_helper(nodes, std::index_sequence_for<Ts...>{}); }
    

    在 C++11/C++14 中稍微复杂一些(没有模板折叠),但您的辅助函数可以模拟如下

    template <typename... Ts, std::size_t ... Is>
    void expand_root_helper (tuple_node<Ts...> nodes, std::index_sequence<Is...>)
     {
       using unused = int[];
    
       (void)unused { 0, ((void)std::get<Is>(nodes.tupl).callback(
                                   std::get<Is>(nodes.tupl).param), 0)... };
     }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-01-25
      • 1970-01-01
      • 1970-01-01
      • 2018-07-11
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多