【问题标题】:Call function for each tuple element on one object without recursion为一个对象上的每个元组元素调用函数,无需递归
【发布时间】:2015-09-08 14:39:57
【问题描述】:

我有一个 A 类的对象,可以使用不同的类型调用它,并在每次调用时返回更改后的 self。对于这个问题,A 会做

struct A {
    A call(const int&) {
    }
    A call(const string& s) {
    }
    ////
} a;

所以我有一个未知类型的元组:

std::tuple<Types...> t;

我想用每个元组元素调用a,所以我想得到类似的东西:

b = a;
b = b.call(get<0>(t));
b = b.call(get<1>(t));
b = b.call(get<2>(t));
//...

b = a.call(get<0>(t)).call(get<1>(t)).call(get<2>(t)...)

顺序并不重要(我的意思是如果调用顺序颠倒了,甚至洗牌也没关系)。

我明白递归是可能的,但它很丑陋。不递归可以实现吗?

【问题讨论】:

  • 我希望有编译时循环以及类似 compile_if( f.has_method( foo ) ) f.foo(); 的东西,但不幸的是,在 c++ 中进行元编程,我们必须处理不同的语言。
  • 我不认为在这种情况下你会发现比使用可变参数模板递归更丑的东西——除非你允许自己使用第三个库(例如boost::fusion)。已经有一个与 SO 密切相关的question
  • 参见:stackoverflow.com/questions/26902633/… 为每个循环提供了一个隐藏所有血腥细节的循环。

标签: c++ tuples c++14 variadic-templates


【解决方案1】:

你可以使用std::index_sequence&lt;Is...&gt;,比如:

namespace detail
{

    template <std::size_t...Is, typename T>
    void a_call(A& a, std::index_sequence<Is...>, const T& t)
    {
        int dummy[] = {0, ((a = a.call(std::get<Is>(t))), void(), 0)...};
        static_cast<void>(dummy); // Avoid warning for unused variable.
    }

}

template <typename ... Ts>
void a_call(A& a, const std::tuple<Ts...>& t)
{
    detail::a_call(a, std::index_sequence_for<Ts...>{}, t);
}

在 C++17 中,折叠表达式允许:

    template <std::size_t...Is, typename T>
    void a_call(A& a, std::index_sequence<Is...>, const T& t)
    {
        (static_cast<void>(a = a.call(std::get<Is>(t))), ...);
    }

甚至,std::apply:

template <typename ... Ts>
void a_call(A& a, const std::tuple<Ts...>& t)
{
    std::apply([&](const auto&... args){ (static_cast<void>(a = a.call(args)), ...); }, t);
}

【讨论】:

  • 多次换a不就是UB吗?
  • @RiaD:我们在一个初始化列表中,它强制从左到右进行评估。
  • @Jarod42 你给出的第一个关于折叠的例子有错字吗?它折叠在哪个运算符上?我想你少了一个逗号。
  • @BrockHargreaves:确实是错别字,加了逗号。
【解决方案2】:

【讨论】:

  • 是的,这可能是最好的方法,除非您想手动进行递归。融合让它变得非常容易。
【解决方案3】:

在“get part of std::tuple”的帮助下:

#include <iostream>
#include <string>
#include <algorithm>
#include <tuple>

//SEE https://stackoverflow.com/questions/8569567/get-part-of-stdtuple 
//vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv

template <size_t... n>
struct ct_integers_list {
    template <size_t m>
    struct push_back
    {
        typedef ct_integers_list<n..., m> type;
    };
};

template <size_t max>
struct ct_iota_1
{
    typedef typename ct_iota_1<max - 1>::type::template push_back<max>::type type;
};

template <>
struct ct_iota_1<0>
{
    typedef ct_integers_list<> type;
};

template <size_t... indices, typename Tuple>
auto tuple_subset(const Tuple& tpl, ct_integers_list<indices...>)
-> decltype(std::make_tuple(std::get<indices>(tpl)...))
{
    return std::make_tuple(std::get<indices>(tpl)...);
    // this means:
    //   make_tuple(get<indices[0]>(tpl), get<indices[1]>(tpl), ...)
}

template <typename Head, typename... Tail>
std::tuple<Tail...> tuple_tail(const std::tuple<Head, Tail...>& tpl)
{
    return tuple_subset(tpl, typename ct_iota_1<sizeof...(Tail)>::type());
    // this means:
    //   tuple_subset<1, 2, 3, ..., sizeof...(Tail)-1>(tpl, ..)
}

//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
//SEE https://stackoverflow.com/questions/8569567/get-part-of-stdtuple 

struct A
{

    A call(const int&) {
        std::cout << "int" << std::endl;
        return *this;
    }

    A call(const std::string& s) {
        std::cout << "string" << std::endl;
        return *this;
    }

    template <typename T>
    A call(std::tuple<T> tpl)
    {
        return call(std::get<0>(tpl));
    }

    template <typename T, typename...Types>
    A call(std::tuple<T, Types...> tpl)
    {       
        return call(std::get<0>(tpl)).call(tuple_tail(tpl));
    }
} a;

int main()
{
    std::tuple<int, std::string, int, int, std::string> t(0, "1", 2, 3, "4");   
    A b = a.call(t);
}

输出:

int
string
int
int
string

【讨论】:

    【解决方案4】:

    如下图使用std::integer_sequence和std::make_integer_sequence:

    struct A {
        A call(const int&) {
            std::cout << "Calling A::call(int)" << std::endl;
            return A{};
        }
        A call(const std::string& s) {
            std::cout << "Calling A::call(std::string)" << std::endl;
            return A{};
        }
        ////
    } a;
    
    template<typename Sequence>
    struct call_helper;
    
    template<std::size_t Current, std::size_t... Rest>
    struct call_helper<std::integer_sequence<std::size_t, Current, Rest...>>
    {
        template<typename Tuple, typename Result>
        static auto call(Tuple const& tup, Result&& res)
        {
            // Call the next helper with the rest sequence
            return call_helper<
                std::integer_sequence<std::size_t, Rest...>
            >::call(tup, res.call(std::get<Current>(tup)));
        }
    };
    
    template<>
    struct call_helper<std::integer_sequence<std::size_t>>
    {
        // End reached, just return the value
        template<typename Tuple, typename Result>
        static auto call(Tuple const&, Result&& res)
            -> std::decay_t<Result>
        {
            return std::forward<Result>(res);
        }
    };
    
    template<typename Tuple, typename Result>
    auto call_all(Tuple const& tup, Result res)
    {
        return call_helper<std::make_integer_sequence<
            std::size_t, std::tuple_size<Tuple>::value>
        >::call(tup, std::forward<Result>(res));
    }
    
    // Test call:
    std::tuple<int, std::string, int, std::string, std::string> mytup;
    call_all(mytup, a);
    

    Demo

    【讨论】:

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