【问题标题】:Template tuple - calling a function on each element模板元组 - 在每个元素上调用一个函数
【发布时间】:2013-05-05 17:53:17
【问题描述】:

我的问题在代码中:

template<typename... Ts>
struct TupleOfVectors {
  std::tuple<std::vector<Ts>...> tuple;

  void do_something_to_each_vec() {
    //Question: I want to do this:
    //  "for each (N)": do_something_to_vec<N>()
    //How?
  }

  template<size_t N>
  void do_something_to_vec() {
    auto &vec = std::get<N>(tuple);
    //do something to vec
  }
};

【问题讨论】:

标签: c++ templates c++11 tuples


【解决方案1】:

您可以使用一些索引机制轻松做到这一点。给定一个元函数gen_seq 用于生成编译时整数序列(由seq 类模板封装):

namespace detail
{
    template<int... Is>
    struct seq { };

    template<int N, int... Is>
    struct gen_seq : gen_seq<N - 1, N - 1, Is...> { };

    template<int... Is>
    struct gen_seq<0, Is...> : seq<Is...> { };
}

以及以下功能模板:

#include <tuple>

namespace detail
{
    template<typename T, typename F, int... Is>
    void for_each(T&& t, F f, seq<Is...>)
    {
        auto l = { (f(std::get<Is>(t)), 0)... };
    }
}

template<typename... Ts, typename F>
void for_each_in_tuple(std::tuple<Ts...> const& t, F f)
{
    detail::for_each(t, f, detail::gen_seq<sizeof...(Ts)>());
}

你可以这样使用上面的for_each_in_tuple函数:

#include <string>
#include <iostream>

struct my_functor
{
    template<typename T>
    void operator () (T&& t)
    {
        std::cout << t << std::endl;
    }
};

int main()
{
    std::tuple<int, double, std::string> t(42, 3.14, "Hello World!");
    for_each_in_tuple(t, my_functor());
}

这是live example

根据您的具体情况,您可以这样使用它:

template<typename... Ts>
struct TupleOfVectors
{
    std::tuple<std::vector<Ts>...> t;

    void do_something_to_each_vec()
    {
        for_each_in_tuple(t, tuple_vector_functor());
    }

    struct tuple_vector_functor
    {
        template<typename T>
        void operator () (T const &v)
        {
            // Do something on the argument vector...
        }
    };
};

再一次,这里是live example

更新

如果您使用的是 C++14 或更高版本,则可以将上面的 seqgen_seq 类替换为 std::integer_sequence,如下所示:

namespace detail
{
    template<typename T, typename F, int... Is>
    void
    for_each(T&& t, F f, std::integer_sequence<int, Is...>)
    {
        auto l = { (f(std::get<Is>(t)), 0)... };
    }
} // namespace detail

template<typename... Ts, typename F>
void
for_each_in_tuple(std::tuple<Ts...> const& t, F f)
{
    detail::for_each(t, f, std::make_integer_sequence<int, sizeof...(Ts)>());
}

如果您使用的是 C++17 或更高版本,您可以这样做(来自this comment below):

std::apply([](auto ...x){std::make_tuple(some_function(x)...);} , the_tuple);

【讨论】:

  • 元编程正在吞噬我的大脑。不知道事情从哪里开始,什么叫什么,以及最终结果是什么/在哪里。根本不像常规编程。需要一些时间来破译这个:)
  • @7cows:当然。一开始这并不容易,也许这不是最简单的例子,但我相信通过一些练习你很快就会掌握它;)
  • void for_each(T&amp;&amp; t, F f, seq&lt;Is...&gt;): 为什么最后一个参数没有标识符?
  • auto l 允许将右侧的内容视为初始值设定项列表,以确保以正确的顺序评估扩展表达式。 (f(x), 0) 使用逗号运算符,以便计算 f(x),但结果值被丢弃,表达式 (f(x), 0) 的值是 0。这是为了让初始化列表的所有元素都具有相同的类型(int,此处),以便能够推断出初始化列表的类型(initializer_list&lt;int&gt;
  • 这么多年过去了,第一部分能不能换成std::integer_sequence
【解决方案2】:

在 C++17 中,您可以这样做:

std::apply([](auto ...x){std::make_tuple(some_function(x)...);} , the_tuple);

鉴于some_function 对元组中的所有类型都有合适的重载。

这已经在 Clang++ 3.9 中工作了,使用 std::experimental::apply

【讨论】:

  • 这不会导致迭代 - 即 do_something() 的调用 - 以未指定的顺序发生,因为参数包在函数调用 () 中展开,其中参数具有未指定的顺序?这可能非常重要;我想大多数人都希望保证以与成员相同的顺序发生排序,即作为std::get&lt;&gt;() 的索引。 AFAIK,为了在这种情况下获得保证订购,扩展必须在{braces} 内完成。我错了吗?这个答案强调这种排序:stackoverflow.com/a/16387374/2757035
  • @underscore_d C++17 保证函数参数的执行顺序。由于此答案适用于 C++17,因此这是有效的。
  • @GuillaumeRacicot 我知道在某些情况下评估顺序/保证的一些变化,但不是函数的参数 - 除了一些从左到右排序的来回 考虑过但拒绝了。查看当前标准草案:github.com/cplusplus/draft/blob/master/source/…The initialization of a parameter, including every associated value computation and side effect, is indeterminately sequenced with respect to that of any other parameter.
  • std::apply([](auto&amp;&amp; ...x){ (static_cast&lt;void&gt;(some_function(std::forward&lt;decltype(x)&gt;(x))), ...);} , the_tuple); 保证评估顺序,并允许some_function 返回void(甚至是重载operator , 的邪恶类)。
  • @Jarod42 因为我们在这里讨论 c++17,所以像 std::apply([](auto&amp; ...x){(..., some_function(x));}, the_tuple); 这样的折叠表达式更好 stackoverflow.com/a/45498003/8414561
【解决方案3】:

除了@M 的the answer。 Aaggan,如果您需要在元组元素上调用一个函数按元组中的出现顺序,在 C++17 中,您还可以使用这样的折叠表达式:

std::apply([](auto& ...x){(..., some_function(x));}, the_tuple);

(live example).

否则order of evaluation of function arguments is unspecified.

【讨论】:

  • 恕我直言,这是一个比公认的更好的解决方案,因为它的语法更清晰,调用顺序与元组顺序相同。
【解决方案4】:

这是一种可能适用于您的情况的方法:

template<typename... Ts>
struct TupleOfVectors {
    std::tuple<std::vector<Ts>...> tuple;

    void do_something_to_each_vec()
    {
        // First template parameter is just a dummy.
        do_something_to_each_vec_helper<0,Ts...>();
    }

    template<size_t N>
    void do_something_to_vec()
    {
        auto &vec = std::get<N>(tuple);
        //do something to vec
    }

private:
    // Anchor for the recursion
    template <int>
    void do_something_to_each_vec_helper() { }

    // Execute the function for each template argument.
    template <int,typename Arg,typename...Args>
    void do_something_to_each_vec_helper()
    {
        do_something_to_each_vec_helper<0,Args...>();
        do_something_to_vec<sizeof...(Args)>();
    }
};

这里唯一有点混乱的是do_something_to_each_vec_helper 的额外虚拟int 模板参数。当没有参数时,有必要使 do_something_to_each_vec_helper 仍然是一个模板。如果你想使用另一个模板参数,你可以在那里使用它。

【讨论】:

  • 它是如何以正确的 0,1,2,... 顺序调用 do_something_to_vec 方法的,这真是太棒了,不是吗?我喜欢它... :)
  • template&lt;typename Arg&gt; void do_something_to_each_vec_helper() { do_something_to_vec&lt;)(); template&lt;typename Arg, typename...Args&gt; void do_something_to_each_vec_helper() { do_something_to_each_vec_helper&lt;Args...&gt;(); do_something_to_vec&lt;sizeof...(Args)&gt;(); } 摆脱了未使用的int,但在某种程度上违反了 DRY(不要重复自己)。嗯。
  • @VaughnCato 真的吗?我认为在模棱两可的情况下,选择了没有参数包的那个。我猜我错了——也许这只适用于它是一组可变参数,而不是纯类型?
【解决方案5】:

如果您不是特别喜欢通用形式的解决方案 “for each”函数模板,那么您可以使用这样的模板:

#ifndef TUPLE_OF_VECTORS_H
#define TUPLE_OF_VECTORS_H

#include <vector>
#include <tuple>
#include <iostream>

template<typename... Ts>
struct TupleOfVectors 
{
    std::tuple<std::vector<Ts>...> tuple;

    template<typename ...Args>
    TupleOfVectors(Args... args)
    : tuple(args...){}

    void do_something_to_each_vec() {
        do_something_to_vec(tuple);
    }

    template<size_t I = 0, class ...P>
    typename std::enable_if<I == sizeof...(P)>::type
    do_something_to_vec(std::tuple<P...> &) {}

    template<size_t I = 0, class ...P>
    typename std::enable_if<I < sizeof...(P)>::type
    do_something_to_vec(std::tuple<P...> & parts) {
        auto & part = std::get<I>(tuple);
        // Doing something...
        std::cout << "vector[" << I << "][0] = " << part[0] << std::endl;
        do_something_to_vec<I + 1>(parts);
    }
};

#endif // EOF

使用 GCC 4.7.2 和 clang 3.2 构建的测试程序:

#include "tuple_of_vectors.h"

using namespace std;

int main()
{
    TupleOfVectors<int,int,int,int> vecs(vector<int>(1,1),
        vector<int>(2,2),
        vector<int>(3,3),
        vector<int>(4,4));

    vecs.do_something_to_each_vec();
    return 0;
}

可以在通用的“for_each”中使用相同的递归样式 无辅助索引器的函数模板:

#ifndef FOR_EACH_IN_TUPLE_H
#define FOR_EACH_IN_TUPLE_H

#include <type_traits>
#include <tuple>
#include <cstddef>

template<size_t I = 0, typename Func, typename ...Ts>
typename std::enable_if<I == sizeof...(Ts)>::type
for_each_in_tuple(std::tuple<Ts...> &, Func) {}

template<size_t I = 0, typename Func, typename ...Ts>
typename std::enable_if<I < sizeof...(Ts)>::type
for_each_in_tuple(std::tuple<Ts...> & tpl, Func func) 
{
    func(std::get<I>(tpl));
    for_each_in_tuple<I + 1>(tpl,func);
}

#endif //EOF

还有一个测试程序:

#include "for_each_in_tuple.h"
#include <iostream>

struct functor
{
    template<typename T>
    void operator () (T&& t)
    {
        std::cout << t << std::endl;
    }
};

int main()
{
    auto tpl = std::make_tuple(1,2.0,"Three");
    for_each_in_tuple(tpl,functor());
    return 0;
}

【讨论】:

  • 这个答案对我更有帮助,它不会像其他一些人那样产生警告。 +1
【解决方案6】:

我正在使用元组和元编程进行测试,并找到了当前线程。 我认为我的工作可以启发其他人,尽管我喜欢@Andy 的解决方案。

不管怎样,开心就好!

#include <tuple>
#include <type_traits>
#include <iostream>
#include <sstream>
#include <functional>

template<std::size_t I = 0, typename Tuple, typename Func>
typename std::enable_if< I != std::tuple_size<Tuple>::value, void >::type
for_each(const Tuple& tuple, Func&& func)
{
    func(std::get<I>(tuple));
    for_each<I + 1>(tuple, func);
}

template<std::size_t I = 0, typename Tuple, typename Func>
typename std::enable_if< I == std::tuple_size<Tuple>::value, void >::type
for_each(const Tuple& tuple, Func&& func)
{
    // do nothing
}


struct print
{
    template<typename T>
    void operator () (T&& t)
    {
        std::cout << t << std::endl;
    }
};

template<typename... Params>
void test(Params&& ... params)
{
    int sz = sizeof...(params);
    std::tuple<Params...> values(std::forward<Params>(params)...);
    for_each(values, print() );
}


class MyClass
{
public:
    MyClass(const std::string& text) 
        : m_text(text)
    {
    }

    friend std::ostream& operator <<(std::ostream& stream, const MyClass& myClass)
    {
        stream << myClass.m_text;
        return stream;
    }

private:
    std::string m_text;
};


int main()
{
    test(1, "hello", 3.f, 4, MyClass("I don't care") );
}

【讨论】:

  • 一段很棒的代码!然而,因为它需要一个左值func,所以它不能与内联 lambda 一起使用。函数签名应更改为for_each(const Tuple&amp; tuple, Func&amp;&amp; func),并带有Func&amp;&amp; func 参数以允许传递临时lambda。
【解决方案7】:

Boost mp11 有这个功能:

#include <iostream>
#include <string>
#include <boost/mp11.hpp>

using namespace std;
using boost::mp11::tuple_for_each;

std::tuple t{string("abc"), 47 };

int main(){
    tuple_for_each(t,[](const auto& x){
        cout << x + x << endl;
    });
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-03-23
    • 1970-01-01
    • 2015-08-15
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多