我做了以下元组:
我想知道如何迭代它?有tupl_size()
,但阅读文档,我没有得到如何利用它。我也搜索了SO,但问题似乎是Boost::tuple
。
auto some = make_tuple("I am good", 255, 2.1);
答案 0 :(得分:28)
template<class F, class...Ts, std::size_t...Is>
void for_each_in_tuple(const std::tuple<Ts...> & tuple, F func, std::index_sequence<Is...>){
using expander = int[];
(void)expander { 0, ((void)func(std::get<Is>(tuple)), 0)... };
}
template<class F, class...Ts>
void for_each_in_tuple(const std::tuple<Ts...> & tuple, F func){
for_each_in_tuple(tuple, func, std::make_index_sequence<sizeof...(Ts)>());
}
用法:
auto some = std::make_tuple("I am good", 255, 2.1);
for_each_in_tuple(some, [](const auto &x) { std::cout << x << std::endl; });
std::index_sequence
和系列是C ++ 14的功能,但它们可以很容易地在C ++ 11中实现(在SO上有很多可用的)。多态lambda也是C ++ 14,但可以用自定义编写的算子代替。
答案 1 :(得分:24)
这是尝试将元组的迭代分解为组件部分。
首先,表示按顺序执行一系列操作的函数。请注意,许多编译器发现这很难理解,尽管它是合法的C ++ 11,据我所知:
template<class... Fs>
void do_in_order( Fs&&... fs ) {
int unused[] = { 0, ( (void)std::forward<Fs>(fs)(), 0 )... }
(void)unused; // blocks warnings
}
接下来,一个函数采用std::tuple
,并提取访问每个元素所需的索引。通过这样做,我们可以在以后完善前进。
作为附带好处,我的代码支持std::pair
和std::array
迭代:
template<class T>
constexpr std::make_index_sequence<std::tuple_size<T>::value>
get_indexes( T const& )
{ return {}; }
肉和土豆:
template<size_t... Is, class Tuple, class F>
void for_each( std::index_sequence<Is...>, Tuple&& tup, F&& f ) {
using std::get;
do_in_order( [&]{ f( get<Is>(std::forward<Tuple>(tup)) ); }... );
}
和面向公众的界面:
template<class Tuple, class F>
void for_each( Tuple&& tup, F&& f ) {
auto indexes = get_indexes(tup);
for_each(indexes, std::forward<Tuple>(tup), std::forward<F>(f) );
}
虽然它指出Tuple
,但它适用于std::array
和std::pair
。它还将所述对象的r / l值类别转发到它调用的函数对象。另请注意,如果您的自定义类型具有免费功能get<N>
,并且覆盖get_indexes
,则上述for_each
将适用于您的自定义类型。
如上所述,do_in_order
虽然很多编译器都不支持,但是他们不喜欢将未扩展参数包扩展到参数包中的lambda。
我们可以在这种情况下内联do_in_order
template<size_t... Is, class Tuple, class F>
void for_each( std::index_sequence<Is...>, Tuple&& tup, F&& f ) {
using std::get;
int unused[] = { 0, ( (void)f(get<Is>(std::forward<Tuple>(tup)), 0 )... }
(void)unused; // blocks warnings
}
这并不会花费太多的冗长,但我个人觉得不太清楚。在我看来,do_in_order
如何运作的阴影魔术在内联中变得模糊不清。
index_sequence
(和支持模板)是一个可以用C ++ 11编写的C ++ 14特性。在堆栈溢出上找到这样的实现很容易。目前最热门的谷歌搜索是a decent O(lg(n)) depth implementation,如果我正确阅读评论可能是实际gcc make_integer_sequence
的至少一次迭代的基础(评论还指出了一些进一步的编译时改进)消除sizeof...
次来电。)
或者我们可以写:
template<class F, class...Args>
void for_each_arg(F&&f,Args&&...args){
using discard=int[];
(void)discard{0,((void)(
f(std::forward<Args>(args))
),0)...};
}
然后:
template<size_t... Is, class Tuple, class F>
void for_each( std::index_sequence<Is...>, Tuple&& tup, F&& f ) {
using std::get;
for_each_arg(
std::forward<F>(f),
get<Is>(std::forward<Tuple>(tup))...
);
}
这避免了手动扩展,但编译更多的编译器。我们通过Is
参数传递auto&&i
。
在C ++ 1z中,我们也可以使用std::apply
和for_each_arg
函数对象来取消索引摆弄。
答案 2 :(得分:10)
这是一个类似的,比TC先前接受的更详细的解决方案,它可能更容易理解( - 它可能与网络中的其他数千个相同):
template<typename TupleType, typename FunctionType>
void for_each(TupleType&&, FunctionType
, std::integral_constant<size_t, std::tuple_size<typename std::remove_reference<TupleType>::type >::value>) {}
template<std::size_t I, typename TupleType, typename FunctionType
, typename = typename std::enable_if<I!=std::tuple_size<typename std::remove_reference<TupleType>::type>::value>::type >
void for_each(TupleType&& t, FunctionType f, std::integral_constant<size_t, I>)
{
f(std::get<I>(t));
for_each(std::forward<TupleType>(t), f, std::integral_constant<size_t, I + 1>());
}
template<typename TupleType, typename FunctionType>
void for_each(TupleType&& t, FunctionType f)
{
for_each(std::forward<TupleType>(t), f, std::integral_constant<size_t, 0>());
}
用法(使用std::tuple
):
auto some = std::make_tuple("I am good", 255, 2.1);
for_each(some, [](const auto &x) { std::cout << x << std::endl; });
用法(使用std::array
):
std::array<std::string,2> some2 = {"Also good", "Hello world"};
for_each(some2, [](const auto &x) { std::cout << x << std::endl; });
一般想法:,如在T.C.的解决方案中,从索引I=0
开始,并达到元组的大小。但是,这里不是每次可变扩展而是一次一个地进行。
<强>解释强>
如果for_each
等于元组的大小,则调用I
的第一个重载。然后函数什么都不做,这样就结束了递归。
第二个重载使用参数std::get<I>(t)
调用函数,并将索引增加1。需要类std::integral_constant
才能在编译时解析I
的值。 std::enable_if
SFINAE内容用于帮助编译器将此重载与前一个重载分开,并且只有在I
小于元组大小时调用此重载(在Coliru上这是需要的,而在Visual中) Studio没有工作。
第三个用I=0
启动递归。通常从外部调用过载。
编辑:我还提到了Yakk提到的通过使用通用模板参数std::array
来额外支持std::pair
和TupleType
的想法一个专门用于std::tuple<Ts ...>
的。
由于TupleType
类型需要推断并且是这样的通用引用&#34;,这进一步具有免费获得完美转发的优势。缺点是必须通过typename std::remove_reference<TupleType>::type
使用另一个间接,因为TupleType
也可能被推断为参考类型。