如何迭代 std::tuple 的元素?
Posted
技术标签:
【中文标题】如何迭代 std::tuple 的元素?【英文标题】:How can you iterate over the elements of an std::tuple? 【发布时间】:2010-11-14 22:58:54 【问题描述】:如何迭代元组(使用 C++11)?我尝试了以下方法:
for(int i=0; i<std::tuple_size<T...>::value; ++i)
std::get<i>(my_tuple).do_sth();
但这不起作用:
错误 1:抱歉,未实现:无法将“Listener ...”扩展为固定长度的参数列表。 错误 2:i 不能出现在常量表达式中。
那么,如何正确迭代元组的元素?
【问题讨论】:
请问,你是怎么用C++0x编译的?据我所知,它还没有发布也没有准备好。 g++ 从 4.3 版开始包含对一些 C++0X 特性的实验性支持,包括可变参数模板。其他编译器也这样做(具有不同的功能集,如果你想在生产中使用它们,你回到了 90 年代,对最前沿的东西提供了广泛的支持) 我正在使用 g++ 4.4 版和 std=c++0x 这个问题需要 C++11 更新。 @Omnifarious 现在需要C++14 update 【参考方案1】:我有一个基于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);
通常的想法是使用编译时递归。事实上,这个想法被用来制作一个类型安全的 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);
尽管这需要一些努力才能让FuncT
表示具有适合元组可能包含的每种类型的重载的东西。如果您知道所有元组元素都将共享一个公共基类或类似的东西,那么这种方法效果最好。
【讨论】:
感谢您提供的简单示例。对于想了解其工作原理的 C++ 初学者,请参阅 SFINAE 和enable_if
documentation。
这可以很容易地概括为一个通用的for_each
。事实上,我是自己做的。 :-) 我认为如果它已经被概括,这个答案会更有用。
这里,我添加了概括,因为我确实需要一个,我认为它对其他人来说会很有用。
注意:您可能还需要带有const std::tuple<Tp...>&
..的版本。如果您不打算在迭代时修改元组,那些const
版本就足够了。
不像写的那样。你可以制作一个索引翻转的版本 - 从 I=sizeof...(Tp) 开始并倒计时。然后显式提供最大数量的参数。你也可以制作一个破坏标签类型的版本,比如break_t。然后,当您想停止打印时,您可以将该标记类型的对象放入您的元组中。或者您可以提供一个停止类型作为模板参数。显然你不能在运行时中断。【参考方案2】:
在 C++17 中,您可以使用 std::apply
和 fold expression:
std::apply([](auto&&... args) ((/* args.dosomething() */), ...);, the_tuple);
打印元组的完整示例:
#include <tuple>
#include <iostream>
int main()
std::tuple t42, 'a', 4.2; // Another C++17 feature: class template argument deduction
std::apply([](auto&&... args) ((std::cout << args << '\n'), ...);, t);
[Online Example on Coliru]
此方案解决了M. Alaggan's answer中的求值顺序问题。
【讨论】:
您能解释一下这里发生了什么:((std::cout << args << '\n'), ...);
吗? lambda 被调用一次,元组元素解包为args
,但是双括号是怎么回事?
@helmesjo 这里扩展为逗号表达式((std::cout << arg1 << '\n'), (std::cout << arg2 << '\n'), (std::cout << arg3 << '\n'))
。
请注意,如果您想在逗号表达式中做一些不合法的事情(例如声明变量和块),您可以将所有这些放入一个方法中并简单地从在折叠逗号表达式中。【参考方案3】:
Boost.Fusion 是一种可能性:
未经测试的示例:
struct DoSomething
template<typename T>
void operator()(T& t) const
t.do_sth();
;
tuple<....> t = ...;
boost::fusion::for_each(t, DoSomething());
【讨论】:
@ViktorSehr AFAICT 它没有(至少在 GCC 4.7.2 上)?有人有提示吗? @ViktorSehr 发现问题:一个错误/遗漏导致融合的行为取决于包含的顺序,请参阅 Boost ticket #8418 了解更多详情 需要使用 boost::fusion::tuple 而不是 std::tuple 才能正常工作。 在 GCC 8.1/mingw-64 下,我收到两个关于使用带有 std lambda 表达式的 boost::fusion::for_each 的警告: boost/mpl/assert.hpp:188:21: 警告: 'assert_arg' [-Wparentheses] 声明中不必要的括号失败 ************ (Pred::************ boost/mpl/assert.hpp:193 :21: 警告:'assert_not_arg' [-Wparentheses] 声明中不必要的括号失败 ************ (boost::mpl::not_在 C++17 中,您可以这样做:
std::apply([](auto ...x)std::make_tuple(x.do_something()...); , the_tuple);
这已经在 Clang++ 3.9 中工作,使用 std::experimental::apply。
【讨论】:
这不会导致迭代 - 即do_something()
的调用 - 以未指定的顺序发生,因为参数包在函数调用 ()
中展开,其中参数具有未指定的顺序?这可能非常重要;我想大多数人都希望保证以与成员相同的顺序发生排序,即作为std::get<>()
的索引。 AFAIK,为了在这种情况下获得保证订购,扩展必须在braces
内完成。我错了吗?这个答案强调这种排序:***.com/a/16387374/2757035【参考方案5】:
C++ 为此目的引入了expansion statements。他们最初在 C++20 的轨道上,但由于缺乏语言措辞审查的时间而差点错过(参见 here 和 here)。
目前商定的语法(见上面的链接)是:
auto tup = std::make_tuple(0, 'a', 3.14);
template for (auto elem : tup)
std::cout << elem << std::endl;
【讨论】:
【参考方案6】:使用 Boost.Hana 和通用 lambda:
#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
【讨论】:
请不要去using namespace boost::fusion
(尤其是和using namespace std
一起)。现在无法知道for_each
是std::for_each
还是boost::fusion::for_each
@Bulletmagnet 这样做是为了简洁,ADL 可以毫无问题地处理它。此外,它也是本地函数。【参考方案7】:
在 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);
这是编译时递归,类似于@emsr 提出的递归。但这不使用 SFINAE,所以(我认为)它对编译器更友好。
【讨论】:
【参考方案8】:这是一种简单的 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)...);
例子:
#include <iostream>
int main()
std::tuple<int, char> items1, 'a';
for_each(items, [](const auto& item)
std::cout << item << "\n";
);
输出:
1
a
这可以扩展为在可调用对象返回值的情况下有条件地中断循环(但仍适用于不返回布尔可分配值的可调用对象,例如 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)...);
例子:
#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;
);
输出:
1
a
---
1
【讨论】:
【参考方案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;
在 C++0x 中,您可以将 print_tuple()
编写为可变参数模板函数。
【讨论】:
【参考方案10】:首先定义一些索引助手:
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...> ;
您希望在每个元组元素上应用您的函数:
template <typename T>
/* ... */ foo(T t) /* ... */
你可以写:
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)>());
或者如果foo
返回void
,使用
std::tie((foo(std::get<I>(ts)), 1) ... );
注意:在 C++14 上,make_index_sequence
已定义 (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)>());
【讨论】:
应该在调用operator,
之前将foo
的返回值转换为void
以避免可能的病态运算符重载。【参考方案11】:
如果你想使用 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) );
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;
可变参数模板的力量!
【讨论】:
我尝试了您的第一个解决方案,但在配对时使用此功能失败。知道为什么吗?template在 MSVC STL 中有一个 _For_each_tuple_element 函数(未记录):
#include <tuple>
// ...
std::tuple<int, char, float> values;
std::_For_each_tuple_element(values, [](auto&& value)
// process 'value'
);
【讨论】:
【参考方案13】:其他人提到了一些设计精良的第三方库,您可能会求助于这些库。但是,如果您在没有这些第三方库的情况下使用 C++,以下代码可能会有所帮助。
namespace detail
template <class Tuple, std::size_t I, class = void>
struct for_each_in_tuple_helper
template <class UnaryFunction>
static void apply(Tuple&& tp, UnaryFunction& f)
f(std::get<I>(std::forward<Tuple>(tp)));
for_each_in_tuple_helper<Tuple, I + 1u>::apply(std::forward<Tuple>(tp), f);
;
template <class Tuple, std::size_t I>
struct for_each_in_tuple_helper<Tuple, I, typename std::enable_if<
I == std::tuple_size<typename std::decay<Tuple>::type>::value>::type>
template <class UnaryFunction>
static void apply(Tuple&&, UnaryFunction&)
;
// namespace detail
template <class Tuple, class UnaryFunction>
UnaryFunction for_each_in_tuple(Tuple&& tp, UnaryFunction f)
detail::for_each_in_tuple_helper<Tuple, 0u>
::apply(std::forward<Tuple>(tp), f);
return std::move(f);
注意:代码可以用任何支持C++11的编译器编译,并且与标准库的设计保持一致:
元组不必是std::tuple
,而是可以是任何支持std::get
和std::tuple_size
的东西;特别是可以使用std::array
和std::pair
;
元组可以是引用类型或 cv 限定的;
与std::for_each
行为类似,返回输入UnaryFunction
;
对于 C++14(或最新版本)用户,typename std::enable_if<T>::type
和 typename std::decay<T>::type
可以替换为其简化版本 std::enable_if_t<T>
和 std::decay_t<T>
;
对于 C++17(或最新版本)用户,std::tuple_size<T>::value
可以替换为其简化版本 std::tuple_size_v<T>
。
对于 C++20(或最新版本)用户,SFINAE
功能可以通过 Concepts
实现。
【讨论】:
【参考方案14】:使用constexpr
和if constexpr
(C++17) 这相当简单直接:
template <std::size_t I = 0, typename ... Ts>
void print(std::tuple<Ts...> tup)
if constexpr (I == sizeof...(Ts))
return;
else
std::cout << std::get<I>(tup) << ' ';
print<I+1>(tup);
【讨论】:
如果我们有if constexpr
,we also have std::apply()
,这比使用递归模板更容易。【参考方案15】:
另一种选择是为元组实现迭代器。这样做的好处是您可以使用标准库和基于范围的 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);
);
我的实现(很大程度上基于上面链接中的解释):
#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
通过将const std::tuple<>&
传递给to_range()
也支持只读访问。
【讨论】:
【参考方案16】:我可能错过了这趟火车,但这里将供将来参考。 这是我基于 answer 和 gist 的构造:
#include <tuple>
#include <utility>
template<std::size_t N>
struct tuple_functor
template<typename T, typename F>
static void run(std::size_t i, T&& t, F&& f)
const std::size_t I = (N - 1);
switch(i)
case I:
std::forward<F>(f)(std::get<I>(std::forward<T>(t)));
break;
default:
tuple_functor<I>::run(i, std::forward<T>(t), std::forward<F>(f));
;
template<>
struct tuple_functor<0>
template<typename T, typename F>
static void run(std::size_t, T, F)
;
然后按如下方式使用它:
template<typename... T>
void logger(std::string format, T... args) //behaves like C#'s String.Format()
auto tp = std::forward_as_tuple(args...);
auto fc = [](const auto& t)std::cout << t;;
/* ... */
std::size_t some_index = ...
tuple_functor<sizeof...(T)>::run(some_index, tp, fc);
/* ... */
可能还有改进的余地。
根据 OP 的代码,它会变成这样:
const std::size_t num = sizeof...(T);
auto my_tuple = std::forward_as_tuple(t...);
auto do_sth = [](const auto& elem)/* ... */;
for(int i = 0; i < num; ++i)
tuple_functor<num>::run(i, my_tuple, do_sth);
【讨论】:
【参考方案17】:在我在这里看到的所有答案中,here 和 here,我最喜欢 @sigidagi 的迭代方式。不幸的是,他的回答非常冗长,我认为这掩盖了内在的清晰性。
这是我的他的解决方案版本,它更简洁,适用于std::tuple
、std::pair
和std::array
。
template<typename UnaryFunction>
void invoke_with_arg(UnaryFunction)
/**
* Invoke the unary function with each of the arguments in turn.
*/
template<typename UnaryFunction, typename Arg0, typename... Args>
void invoke_with_arg(UnaryFunction f, Arg0&& a0, Args&&... as)
f(std::forward<Arg0>(a0));
invoke_with_arg(std::move(f), std::forward<Args>(as)...);
template<typename Tuple, typename UnaryFunction, std::size_t... Indices>
void for_each_helper(Tuple&& t, UnaryFunction f, std::index_sequence<Indices...>)
using std::get;
invoke_with_arg(std::move(f), get<Indices>(std::forward<Tuple>(t))...);
/**
* Invoke the unary function for each of the elements of the tuple.
*/
template<typename Tuple, typename UnaryFunction>
void for_each(Tuple&& t, UnaryFunction f)
using size = std::tuple_size<typename std::remove_reference<Tuple>::type>;
for_each_helper(
std::forward<Tuple>(t),
std::move(f),
std::make_index_sequence<size::value>()
);
演示:coliru
C++14的std::make_index_sequence
可以实现for C++11。
【讨论】:
【参考方案18】:扩展@Stypox 答案,我们可以使他们的解决方案更通用(C++17 以后)。通过添加可调用函数参数:
template<size_t I = 0, typename... Tp, typename F>
void for_each_apply(std::tuple<Tp...>& t, F &&f)
f(std::get<I>(t));
if constexpr(I+1 != sizeof...(Tp))
for_each_apply<I+1>(t, std::forward<F>(f));
那么,我们需要一个策略来访问每种类型。
让我们从一些助手开始(前两个取自 cppreference):
template<class... Ts> struct overloaded : Ts... using Ts::operator()...; ;
template<class... Ts> overloaded(Ts...) -> overloaded<Ts...>;
template<class ... Ts> struct variant_ref using type = std::variant<std::reference_wrapper<Ts>...>; ;
variant_ref
用于允许修改元组的状态。
用法:
std::tuple<Foo, Bar, Foo> tuples;
for_each_apply(tuples,
[](variant_ref<Foo, Bar>::type &&v)
std::visit(overloaded
[](Foo &arg) arg.foo(); ,
[](Bar const &arg) arg.bar(); ,
, v);
);
结果:
Foo0
Bar
Foo0
Foo1
Bar
Foo1
为了完整起见,这里是我的Bar
和Foo
:
struct Foo
void foo() std::cout << "Foo" << i++ << std::endl;
int i = 0;
;
struct Bar
void bar() const std::cout << "Bar" << std::endl;
;
【讨论】:
【参考方案19】:我在迭代一组函数对象时偶然发现了同样的问题,所以这里还有一个解决方案:
#include <tuple>
#include <iostream>
// Function objects
class A
public:
inline void operator()() const std::cout << "A\n"; ;
;
class B
public:
inline void operator()() const std::cout << "B\n"; ;
;
class C
public:
inline void operator()() const std::cout << "C\n"; ;
;
class D
public:
inline void operator()() const std::cout << "D\n"; ;
;
// Call iterator using recursion.
template<typename Fobjects, int N = 0>
struct call_functors
static void apply(Fobjects const& funcs)
std::get<N>(funcs)();
// Choose either the stopper or descend further,
// depending if N + 1 < size of the tuple.
using caller = std::conditional_t
<
N + 1 < std::tuple_size_v<Fobjects>,
call_functors<Fobjects, N + 1>,
call_functors<Fobjects, -1>
>;
caller::apply(funcs);
;
// Stopper.
template<typename Fobjects>
struct call_functors<Fobjects, -1>
static void apply(Fobjects const& funcs)
;
// Call dispatch function.
template<typename Fobjects>
void call(Fobjects const& funcs)
call_functors<Fobjects>::apply(funcs);
;
using namespace std;
int main()
using Tuple = tuple<A,B,C,D>;
Tuple functors = A, B, C, D;
call(functors);
return 0;
输出:
A
B
C
D
【讨论】:
【参考方案20】:boost 的元组提供了辅助函数 get_head()
和 get_tail()
,所以你的辅助函数可能看起来像这样:
inline void call_do_sth(const null_type&) ;
template <class H, class T>
inline void call_do_sth(cons<H, T>& x) x.get_head().do_sth(); call_do_sth(x.get_tail());
如此处所述http://www.boost.org/doc/libs/1_34_0/libs/tuple/doc/tuple_advanced_interface.html
std::tuple
应该是相似的。
实际上,很遗憾std::tuple
似乎没有提供这样的接口,因此之前建议的方法应该可以工作,或者您需要切换到具有其他好处的boost::tuple
(例如已经提供的io 运算符)。尽管使用 gcc 有 boost::tuple
的缺点 - 它还不接受可变参数模板,但这可能已经修复,因为我的机器上没有安装最新版本的 boost。
【讨论】:
以上是关于如何迭代 std::tuple 的元素?的主要内容,如果未能解决你的问题,请参考以下文章
如何在 std::tuple 中合并 std::unordered_map?