C++ 如何遍历 std::tuple 的元素?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/1198260/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-27 19:08:56  来源:igfitidea点击:

How can you iterate over the elements of an std::tuple?

c++c++11iterationtemplate-meta-programmingstdtuple

提问by emsr

How can I iterate over a tuple (using C++11)? I tried the following:

如何迭代元组(使用 C++11)?我尝试了以下方法:

for(int i=0; i<std::tuple_size<T...>::value; ++i) 
  std::get<i>(my_tuple).do_sth();

but this doesn't work:

但这不起作用:

Error 1: sorry, unimplemented: cannot expand ‘Listener ...' into a fixed-length argument list.
Error 2: i cannot appear in a constant expression.

错误 1:抱歉,未实现:无法将 'Listener ...' 扩展为固定长度的参数列表。
错误 2:i 不能出现在常量表达式中。

So, how do I correctly iterate over the elements of a tuple?

那么,如何正确迭代元组的元素?

采纳答案by éric Malenfant

Boost.Fusionis a possibility:

Boost.Fusion是一种可能性:

Untested example:

未经测试的示例:

struct DoSomething
{
    template<typename T>
    void operator()(T& t) const
    {
        t.do_sth();
    }
};

tuple<....> t = ...;
boost::fusion::for_each(t, DoSomething());

回答by emsr

I have an answer based on Iterating over a Tuple:

我有一个基于迭代元组的答案:

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

template<std::size_t I = 0, typename... Tp>
inline typename std::enable_if<I == sizeof...(Tp), void>::type
  print(std::tuple<Tp...>& t)
  { }

template<std::size_t I = 0, typename... Tp>
inline typename std::enable_if<I < sizeof...(Tp), void>::type
  print(std::tuple<Tp...>& t)
  {
    std::cout << std::get<I>(t) << std::endl;
    print<I + 1, Tp...>(t);
  }

int
main()
{
  typedef std::tuple<int, float, double> T;
  T t = std::make_tuple(2, 3.14159F, 2345.678);

  print(t);
}

The usual idea is to use compile time recursion. In fact, this idea is used to make a printf that is type safe as noted in the original tuple papers.

通常的想法是使用编译时递归。事实上,这个想法被用来制作一个类型安全的 printf,如原始元组论文中所述。

This can be easily generalized into a for_eachfor tuples:

这可以很容易地推广为一个for_eachfor 元组:

#include <tuple>
#include <utility> 

template<std::size_t I = 0, typename FuncT, typename... Tp>
inline typename std::enable_if<I == sizeof...(Tp), void>::type
  for_each(std::tuple<Tp...> &, FuncT) // Unused arguments are given no names.
  { }

template<std::size_t I = 0, typename FuncT, typename... Tp>
inline typename std::enable_if<I < sizeof...(Tp), void>::type
  for_each(std::tuple<Tp...>& t, FuncT f)
  {
    f(std::get<I>(t));
    for_each<I + 1, FuncT, Tp...>(t, f);
  }

Though this then requires some effort to have FuncTrepresent something with the appropriate overloads for every type the tuple might contain. This works best if you know all the tuple elements will share a common base class or something similar.

尽管这需要一些努力来FuncT为元组可能包含的每种类型表示具有适当重载的内容。如果您知道所有元组元素将共享一个共同的基类或类似的东西,这将最有效。

回答by xskxzr

In C++17, you can use std::applywith fold expression:

在 C++17 中,您可以使用std::applywith fold 表达式

std::apply([](auto&&... args) {((/* args.dosomething() */), ...);}, the_tuple);

A complete example for printing a tuple:

打印元组的完整示例:

#include <tuple>
#include <iostream>

int main()
{
    std::tuple t{42, 'a', 4.2}; // Another C++17 feature: class template argument deduction
    std::apply([](auto&&... args) {((std::cout << args << '\n'), ...);}, t);
}

[Online Example on Coliru]

[Coliru 在线示例]

This solution solves the issue of evaluation order in M. Alaggan's answer.

该解决方案解决了M. Alaggan 的回答中的求值顺序问题

回答by Mohammad Alaggan

In C++17 you can do this:

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

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

This already works in Clang++ 3.9, using std::experimental::apply.

这已经适用于 Clang++ 3.9,使用 std::experimental::apply。

回答by pepper_chico

Use Boost.Hana and generic lambdas:

使用 Boost.Hana 和通用 lambdas:

#include <tuple>
#include <iostream>
#include <boost/hana.hpp>
#include <boost/hana/ext/std/tuple.hpp>

struct Foo1 {
    int foo() const { return 42; }
};

struct Foo2 {
    int bar = 0;
    int foo() { bar = 24; return bar; }
};

int main() {
    using namespace std;
    using boost::hana::for_each;

    Foo1 foo1;
    Foo2 foo2;

    for_each(tie(foo1, foo2), [](auto &foo) {
        cout << foo.foo() << endl;
    });

    cout << "foo2.bar after mutation: " << foo2.bar << endl;
}

http://coliru.stacked-crooked.com/a/27b3691f55caf271

http://coliru.stacked-crooked.com/a/27b3691f55caf271

回答by DanielS

C++ is introducing expansion statementsfor this purpose. They were originally on track for C++20 but narrowly missed the cut due to a lack of time for language wording review (see hereand here).

为此,C++ 引入了扩展语句。他们最初是在 C++20 的轨道上,但由于缺乏语言措辞的时间,差点错过了晋级(见这里这里)。

The currently agreed syntax (see the links above) is:

当前商定的语法(见上面的链接)是:

{
    auto tup = std::make_tuple(0, 'a', 3.14);
    template for (auto elem : tup)
        std::cout << elem << std::endl;
}

回答by Stypox

A more simple, intuitive and compiler-friendly way of doing this in C++17, using if constexpr:

一种在 C++17 中执行此操作的更简单、直观和编译器友好的方法,使用if constexpr

// prints every element of a tuple
template<size_t I = 0, typename... Tp>
void print(std::tuple<Tp...>& t) {
    std::cout << std::get<I>(t) << " ";
    // do things
    if constexpr(I+1 != sizeof...(Tp))
        print<I+1>(t);
}

This is compile-time recursion, similar to the one presented by @emsr. But this doesn't use SFINAE so (I think) it is more compiler-friendly.

这是编译时递归,类似于@emsr 提供的递归。但这不使用 SFINAE 所以(我认为)它对编译器更友好。

回答by user1447257

First define some index helpers:

首先定义一些索引助手:

template <size_t ...I>
struct index_sequence {};

template <size_t N, size_t ...I>
struct make_index_sequence : public make_index_sequence<N - 1, N - 1, I...> {};

template <size_t ...I>
struct make_index_sequence<0, I...> : public index_sequence<I...> {};

With your function you would like to apply on each tuple element:

使用您的函数,您希望应用于每个元组元素:

template <typename T>
/* ... */ foo(T t) { /* ... */ }

you can write:

你可以写:

template<typename ...T, size_t ...I>
/* ... */ do_foo_helper(std::tuple<T...> &ts, index_sequence<I...>) {
    std::tie(foo(std::get<I>(ts)) ...);
}

template <typename ...T>
/* ... */ do_foo(std::tuple<T...> &ts) {
    return do_foo_helper(ts, make_index_sequence<sizeof...(T)>());
}

Or if fooreturns void, use

或者如果foo返回void,请使用

std::tie((foo(std::get<I>(ts)), 1) ... );

Note: On C++14 make_index_sequenceis already defined (http://en.cppreference.com/w/cpp/utility/integer_sequence).

注意:在 C++14make_index_sequence上已经定义(http://en.cppreference.com/w/cpp/utility/integer_sequence)。

If you do need a left-to-right evaluation order, consider something like this:

如果您确实需要从左到右的评估顺序,请考虑以下内容:

template <typename T, typename ...R>
void do_foo_iter(T t, R ...r) {
    foo(t);
    do_foo(r...);
}

void do_foo_iter() {}

template<typename ...T, size_t ...I>
void do_foo_helper(std::tuple<T...> &ts, index_sequence<I...>) {
    do_foo_iter(std::get<I>(ts) ...);
}

template <typename ...T>
void do_foo(std::tuple<T...> &ts) {
    do_foo_helper(ts, make_index_sequence<sizeof...(T)>());
}

回答by Marc Mutz - mmutz

You need to use template metaprogramming, here shown with Boost.Tuple:

你需要使用模板元编程,这里用 Boost.Tuple 显示:

#include <boost/tuple/tuple.hpp>
#include <iostream>

template <typename T_Tuple, size_t size>
struct print_tuple_helper {
    static std::ostream & print( std::ostream & s, const T_Tuple & t ) {
        return print_tuple_helper<T_Tuple,size-1>::print( s, t ) << boost::get<size-1>( t );
    }
};

template <typename T_Tuple>
struct print_tuple_helper<T_Tuple,0> {
    static std::ostream & print( std::ostream & s, const T_Tuple & ) {
        return s;
    }
};

template <typename T_Tuple>
std::ostream & print_tuple( std::ostream & s, const T_Tuple & t ) {
    return print_tuple_helper<T_Tuple,boost::tuples::length<T_Tuple>::value>::print( s, t );
}

int main() {

    const boost::tuple<int,char,float,char,double> t( 0, ' ', 2.5f, '\n', 3.1416 );
    print_tuple( std::cout, t );

    return 0;
}

In C++0x, you can write print_tuple()as a variadic template function instead.

在 C++0x 中,您可以print_tuple()改为编写可变参数模板函数。

回答by Marcin Zawiejski

Here's an easy C++17 way of iterating over tuple items with just standard library:

这是仅使用标准库迭代元组项的简单 C++17 方法:

#include <tuple>      // std::tuple
#include <functional> // std::invoke

template <
    size_t Index = 0, // start iteration at 0 index
    typename TTuple,  // the tuple type
    size_t Size =
        std::tuple_size_v<
            std::remove_reference_t<TTuple>>, // tuple size
    typename TCallable, // the callable to bo invoked for each tuple item
    typename... TArgs   // other arguments to be passed to the callable 
>
void for_each(TTuple&& tuple, TCallable&& callable, TArgs&&... args)
{
    if constexpr (Index < Size)
    {
        std::invoke(callable, args..., std::get<Index>(tuple));

        if constexpr (Index + 1 < Size)
            for_each<Index + 1>(
                std::forward<TTuple>(tuple),
                std::forward<TCallable>(callable),
                std::forward<TArgs>(args)...);
    }
}

Example:

例子:

#include <iostream>

int main()
{
    std::tuple<int, char> items{1, 'a'};
    for_each(items, [](const auto& item) {
        std::cout << item << "\n";
    });
}

Output:

输出:

1
a

This can be extended to conditionally break the loop in case the callable returns a value (but still work with callables that do not return a bool assignable value, e.g. void):

这可以扩展为在可调用对象返回值的情况下有条件地中断循环(但仍然适用于不返回 bool 可分配值的可调用对象,例如 void):

#include <tuple>      // std::tuple
#include <functional> // std::invoke

template <
    size_t Index = 0, // start iteration at 0 index
    typename TTuple,  // the tuple type
    size_t Size =
    std::tuple_size_v<
    std::remove_reference_t<TTuple>>, // tuple size
    typename TCallable, // the callable to bo invoked for each tuple item
    typename... TArgs   // other arguments to be passed to the callable 
    >
    void for_each(TTuple&& tuple, TCallable&& callable, TArgs&&... args)
{
    if constexpr (Index < Size)
    {
        if constexpr (std::is_assignable_v<bool&, std::invoke_result_t<TCallable&&, TArgs&&..., decltype(std::get<Index>(tuple))>>)
        {
            if (!std::invoke(callable, args..., std::get<Index>(tuple)))
                return;
        }
        else
        {
            std::invoke(callable, args..., std::get<Index>(tuple));
        }

        if constexpr (Index + 1 < Size)
            for_each<Index + 1>(
                std::forward<TTuple>(tuple),
                std::forward<TCallable>(callable),
                std::forward<TArgs>(args)...);
    }
}

Example:

例子:

#include <iostream>

int main()
{
    std::tuple<int, char> items{ 1, 'a' };
    for_each(items, [](const auto& item) {
        std::cout << item << "\n";
    });

    std::cout << "---\n";

    for_each(items, [](const auto& item) {
        std::cout << item << "\n";
        return false;
    });
}

Output:

输出:

1
a
---
1