你如何迭代std :: tuple的元素?

92 c++ iteration template-meta-programming c++11 stdtuple

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

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

但这不起作用:

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

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

ems*_*msr 123

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

#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);
}
Run Code Online (Sandbox Code Playgroud)

通常的想法是使用编译时递归.实际上,这个想法用于制作类型安全的printf,如原始元组文件中所述.

这可以很容易地推广到一个for_each元组:

#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);
  }
Run Code Online (Sandbox Code Playgroud)

虽然这需要付出一些努力来FuncT代表元组可能包含的每种类型的适当重载.如果您知道所有元组元素将共享公共基类或类似内容,则此方法效果最佳.

  • 谢谢你的简单例子.对于寻找有关其工作方式背景的C++初学者,请参阅[SFINAE](http://en.wikipedia.org/wiki/Substitution_failure_is_not_an_error)和[`enable_if`文档](http://en.cppreference.com/w/ CPP /类型/ enable_if). (4认同)
  • 在那里,我添加了概括,因为我实际上需要一个,我认为其他人看到它是有用的. (4认同)
  • 注意:您可能还需要带有`const std :: tuple <Tp ...>&`的版本.如果您不打算在迭代时修改元组,那些`const`版本就足够了. (2认同)
  • 不是写的..你可以制作一个翻转索引的版本 - 从I = sizeof ...(Tp)开始并倒计时.然后显式提供最大数量的args.你也可以制作一个破坏标签类型的版本,比如break_t.然后,当您想要停止打印时,您将在该元组中放置该标记类型的对象.或者您可以提供停止类型作为模板parm.显然你不能在运行时打破. (2认同)

xsk*_*xzr 38

在C++ 17,可以使用std::apply倍的表达:

std::apply([](auto&&... args) {((/* args.dosomething() */), ...);}, the_tuple);
Run Code Online (Sandbox Code Playgroud)

打印元组的完整示例:

#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);
}
Run Code Online (Sandbox Code Playgroud)

[Coliru的在线示例]

该解决方案解决了M. Alaggan的答案中的评估顺序问题.

  • 你能解释一下这里发生了什么吗:`((std::cout &lt;&lt; args &lt;&lt; '\n'), ...);` ?lambda 被调用一次,元组元素被解包为“args”,但是双括号是怎么回事? (4认同)
  • @helmesjo它扩展为逗号表达式`(((std :: cout &lt;&lt; arg1 &lt;&lt;'\ n'),(std :: cout &lt;&lt; arg2 &lt;&lt;'\ n'),(std :: cout &lt;&lt; arg3 &lt;&lt;'\ n'))`在这里。 (3认同)
  • 请注意,如果您想要执行逗号表达式中不合法的操作(例如声明变量和块),您可以将所有这些放入一个方法中,然后从折叠逗号表达式中简单地调用它。 (2认同)

Éri*_*ant 24

Boost.Fusion是一种可能性:

未经测试的例子:

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

tuple<....> t = ...;
boost::fusion::for_each(t, DoSomething());
Run Code Online (Sandbox Code Playgroud)

  • 仅供参考,现已修复:https://svn.boost.org/trac/boost/ticket/8418 (3认同)

pep*_*ico 20

使用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;
}
Run Code Online (Sandbox Code Playgroud)

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

  • 请不要使用命名空间boost :: fusion`(特别是与`using namespace std`一起使用).现在没有办法知道`for_each`是`std :: for_each`还是`boost :: fusion :: for_each` (3认同)
  • @Bulletmagnet这是为了简洁而完成的,ADL可以毫无问题地处理它.此外,它还具有本地功能. (2认同)

M. *_*gan 19

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

std::apply([](auto ...x){std::make_tuple(x.do_something()...);} , the_tuple);
Run Code Online (Sandbox Code Playgroud)

这已经在Clang ++ 3.9中使用了std :: experimental :: apply.

  • 这不是导致迭代-即对do_something()的调用-以未指定的顺序发生,因为参数包在函数调用()中被扩展,其中参数具有未指定的顺序?那可能非常重要。我想大多数人都希望可以保证该顺序与成员的顺序相同,即作为`std :: get &lt;&gt;()`的索引。AFAIK,为了在这种情况下保证订购,必须在`{braces}`内进行扩展。我错了吗?该答案将重点放在这种排序上:http://stackoverflow.com/a/16387374/2757035 (3认同)

Dan*_*elS 13

为此,C ++引入了扩展语句。他们最初是C ++ 20的开发者,但由于缺乏时间进行语言措辞审查而几乎错过了晋级(请参见此处此处)。

当前同意的语法(请参见上面的链接)为:

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

  • 还错过了 C++23,需要修改... https://github.com/cplusplus/papers/issues/156 (6认同)

Mar*_*utz 9

你需要使用模板元编程,这里用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;
}
Run Code Online (Sandbox Code Playgroud)

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


Sty*_*pox 9

使用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);
}
Run Code Online (Sandbox Code Playgroud)

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


use*_*257 8

首先定义一些索引助手:

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...> {};
Run Code Online (Sandbox Code Playgroud)

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

template <typename T>
/* ... */ foo(T t) { /* ... */ }
Run Code Online (Sandbox Code Playgroud)

你可以写:

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)>());
}
Run Code Online (Sandbox Code Playgroud)

或者如果foo退货void,请使用

std::tie((foo(std::get<I>(ts)), 1) ... );
Run Code Online (Sandbox Code Playgroud)

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

如果您确实需要从左到右的评估订单,请考虑以下事项:

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)>());
}
Run Code Online (Sandbox Code Playgroud)


Mar*_*ski 6

这是一种仅使用标准库即可遍历元组项的简单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)...);
    }
}
Run Code Online (Sandbox Code Playgroud)

例:

#include <iostream>

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

输出:

1
a
Run Code Online (Sandbox Code Playgroud)

这可以扩展为在可调用对象返回值的情况下有条件地打破循环(但仍可用于不返回布尔可分配值的可调用对象,例如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)...);
    }
}
Run Code Online (Sandbox Code Playgroud)

例:

#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;
    });
}
Run Code Online (Sandbox Code Playgroud)

输出:

1
a
---
1
Run Code Online (Sandbox Code Playgroud)


Flo*_*ler 6

另一种选择是为元组实现迭代器。这样做的优点是您可以使用标准库提供的各种算法和基于范围的 for 循环。这里解释了一种优雅的方法https://foonathan.net/2017/03/tuple-iterator/。基本思想是通过提供迭代器的方法begin()将元组转换为范围。end()迭代器本身返回一个std::variant<...>,然后可以使用 访问它std::visit

这里有一些例子:

auto t = std::tuple{ 1, 2.f, 3.0 };
auto r = to_range(t);

for(auto v : r)
{
    std::visit(unwrap([](auto& x)
        {
            x = 1;
        }), v);
}

std::for_each(begin(r), end(r), [](auto v)
    {
        std::visit(unwrap([](auto& x)
            {
                x = 0;
            }), v);
    });

std::accumulate(begin(r), end(r), 0.0, [](auto acc, auto v)
    {
        return acc + std::visit(unwrap([](auto& x)
        {
            return static_cast<double>(x);
        }), v);
    });

std::for_each(begin(r), end(r), [](auto v)
{
    std::visit(unwrap([](const auto& x)
        {
            std::cout << x << std::endl;
        }), v);
});

std::for_each(begin(r), end(r), [](auto v)
{
    std::visit(overload(
        [](int x) { std::cout << "int" << std::endl; },
        [](float x) { std::cout << "float" << std::endl; },
        [](double x) { std::cout << "double" << std::endl; }), v);
});
Run Code Online (Sandbox Code Playgroud)

我的实现(很大程度上基于上面链接中的解释):

#ifndef TUPLE_RANGE_H
#define TUPLE_RANGE_H

#include <utility>
#include <functional>
#include <variant>
#include <type_traits>

template<typename Accessor>
class tuple_iterator
{
public:
    tuple_iterator(Accessor acc, const int idx)
        : acc_(acc), index_(idx)
    {

    }

    tuple_iterator operator++()
    {
        ++index_;
        return *this;
    }

    template<typename T>
    bool operator ==(tuple_iterator<T> other)
    {
        return index_ == other.index();
    }

    template<typename T>
    bool operator !=(tuple_iterator<T> other)
    {
        return index_ != other.index();
    }

    auto operator*() { return std::invoke(acc_, index_); }

    [[nodiscard]] int index() const { return index_; }

private:
    const Accessor acc_;
    int index_;
};

template<bool IsConst, typename...Ts>
struct tuple_access
{
    using tuple_type = std::tuple<Ts...>;
    using tuple_ref = std::conditional_t<IsConst, const tuple_type&, tuple_type&>;

    template<typename T>
    using element_ref = std::conditional_t<IsConst,
        std::reference_wrapper<const T>,
        std::reference_wrapper<T>>;

    using variant_type = std::variant<element_ref<Ts>...>;
    using function_type = variant_type(*)(tuple_ref);
    using table_type = std::array<function_type, sizeof...(Ts)>;

private:
    template<size_t Index>
    static constexpr function_type create_accessor()
    {
        return { [](tuple_ref t) -> variant_type
        {
            if constexpr (IsConst)
                return std::cref(std::get<Index>(t));
            else
                return std::ref(std::get<Index>(t));
        } };
    }

    template<size_t...Is>
    static constexpr table_type create_table(std::index_sequence<Is...>)
    {
        return { create_accessor<Is>()... };
    }

public:
    static constexpr auto table = create_table(std::make_index_sequence<sizeof...(Ts)>{}); 
};

template<bool IsConst, typename...Ts>
class tuple_range
{
public:
    using tuple_access_type = tuple_access<IsConst, Ts...>;
    using tuple_ref = typename tuple_access_type::tuple_ref;

    static constexpr auto tuple_size = sizeof...(Ts);

    explicit tuple_range(tuple_ref tuple)
        : tuple_(tuple)
    {
    }

    [[nodiscard]] auto begin() const 
    { 
        return tuple_iterator{ create_accessor(), 0 };
    }

    [[nodiscard]] auto end() const 
    { 
        return tuple_iterator{ create_accessor(), tuple_size };
    }

private:
    tuple_ref tuple_;

    auto create_accessor() const
    { 
        return [this](int idx)
        {
            return std::invoke(tuple_access_type::table[idx], tuple_);
        };
    }
};

template<bool IsConst, typename...Ts>
auto begin(const tuple_range<IsConst, Ts...>& r)
{
    return r.begin();
}

template<bool IsConst, typename...Ts>
auto end(const tuple_range<IsConst, Ts...>& r)
{
    return r.end();
}

template <class ... Fs>
struct overload : Fs... {
    explicit overload(Fs&&... fs) : Fs{ fs }... {}
    using Fs::operator()...;

    template<class T>
    auto operator()(std::reference_wrapper<T> ref)
    {
        return (*this)(ref.get());
    }

    template<class T>
    auto operator()(std::reference_wrapper<const T> ref)
    {
        return (*this)(ref.get());
    }
};

template <class F>
struct unwrap : overload<F>
{
    explicit unwrap(F&& f) : overload<F>{ std::forward<F>(f) } {}
    using overload<F>::operator();
};

template<typename...Ts>
auto to_range(std::tuple<Ts...>& t)
{
    return tuple_range<false, Ts...>{t};
}

template<typename...Ts>
auto to_range(const std::tuple<Ts...>& t)
{
    return tuple_range<true, Ts...>{t};
}


#endif
Run Code Online (Sandbox Code Playgroud)

const std::tuple<>&还可以通过传递to来支持只读访问to_range()


sig*_*agi 5

如果你想使用std :: tuple并且你有支持可变参数模板的C++编译器,请尝试下面的代码(用g ++ 4.5测试).这应该是你的问题的答案.

#include <tuple>

// ------------- UTILITY---------------
template<int...> struct index_tuple{}; 

template<int I, typename IndexTuple, typename... Types> 
struct make_indexes_impl; 

template<int I, int... Indexes, typename T, typename ... Types> 
struct make_indexes_impl<I, index_tuple<Indexes...>, T, Types...> 
{ 
    typedef typename make_indexes_impl<I + 1, index_tuple<Indexes..., I>, Types...>::type type; 
}; 

template<int I, int... Indexes> 
struct make_indexes_impl<I, index_tuple<Indexes...> > 
{ 
    typedef index_tuple<Indexes...> type; 
}; 

template<typename ... Types> 
struct make_indexes : make_indexes_impl<0, index_tuple<>, Types...> 
{}; 

// ----------- FOR EACH -----------------
template<typename Func, typename Last>
void for_each_impl(Func&& f, Last&& last)
{
    f(last);
}

template<typename Func, typename First, typename ... Rest>
void for_each_impl(Func&& f, First&& first, Rest&&...rest) 
{
    f(first);
    for_each_impl( std::forward<Func>(f), rest...);
}

template<typename Func, int ... Indexes, typename ... Args>
void for_each_helper( Func&& f, index_tuple<Indexes...>, std::tuple<Args...>&& tup)
{
    for_each_impl( std::forward<Func>(f), std::forward<Args>(std::get<Indexes>(tup))...);
}

template<typename Func, typename ... Args>
void for_each( std::tuple<Args...>& tup, Func&& f)
{
   for_each_helper(std::forward<Func>(f), 
                   typename make_indexes<Args...>::type(), 
                   std::forward<std::tuple<Args...>>(tup) );
}

template<typename Func, typename ... Args>
void for_each( std::tuple<Args...>&& tup, Func&& f)
{
   for_each_helper(std::forward<Func>(f), 
                   typename make_indexes<Args...>::type(), 
                   std::forward<std::tuple<Args...>>(tup) );
}
Run Code Online (Sandbox Code Playgroud)

boost :: fusion是另一种选择,但它需要自己的元组类型:boost :: fusion :: tuple.让我们更好地坚持标准!这是一个测试:

#include <iostream>

// ---------- FUNCTOR ----------
struct Functor 
{
    template<typename T>
    void operator()(T& t) const { std::cout << t << std::endl; }
};

int main()
{
    for_each( std::make_tuple(2, 0.6, 'c'), Functor() );
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

可变参数模板的强大功能!