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

如何迭代元组(使用 C + + 11) :

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

但这不管用:

错误1: 对不起,未实现: 无法将“监听器...”展开为固定长度的参数列表。
错误2: i 不能出现在常量表达式中。

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

115600 次浏览

你需要使用模板超编程,这里显示的是 Boost:

#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()写成一个可变参数模板函数。

Boost 的 tuple 提供了 helper 函数 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()); }

就像这里描述的一样

std::tuple应该是相似的。

实际上,不幸的是,std::tuple似乎没有提供这样的接口,所以之前建议的方法应该可以工作,否则您将需要切换到 boost::tuple,它有其他的好处(如 IO 操作符已经提供)。虽然 boost::tuple与 gcc 有缺点-它不接受可变模板尚未,但这可能已经修复,因为我没有最新版本的升级安装在我的机器上。

如果你想使用 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) );
}

Fusion 是另一种选择,但它需要自己的 tuple 类型: ost: : 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;
}

可变模板的力量!

基于 在 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表示元组可能包含的每种类型的适当重载。如果您知道所有的 tuple 元素将共享一个公共基类或类似的东西,那么这种方法效果最好。

使用 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

首先定义一些索引助手:

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...> {};

您希望对每个 tuple 元素应用函数:

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 + + 上已经定义了 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)>());
}

我可能错过了这趟火车,但这个会留在这里,以备将来参考。
下面是基于这个 回答和这个 大意的结构:

#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);

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

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

这已经可以在 Clang + + 3.9中使用,使用的是 std: : economic: : application。

这里有一个简单的 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 be 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> items{1, 'a'};
for_each(items, [](const auto& item) {
std::cout << item << "\n";
});
}

产出:

1
a

如果可调用程序返回一个值(但仍然可以处理不返回 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)...);
}
}

例如:

#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

在 MSVC STL 中有一个 _ For _ each _ tuple _ element 函数(没有文档说明) :

#include <tuple>


// ...


std::tuple<int, char, float> values{};
std::_For_each_tuple_element(values, [](auto&& value)
{
// process 'value'
});

在我在这里看到的所有答案中,给你给你,我最喜欢 @ sigidagi的迭代方式。不幸的是,他的回答非常冗长,在我看来,这模糊了固有的清晰性。

这是我的版本,他的解决方案,这是更简洁,并与 std::tuplestd::pairstd::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>()
);
}

演示: 科利鲁

C + + 14的 std::make_index_sequence可以实现 C + + 11

我在迭代一组函数对象时碰到过同样的问题,所以这里有一个解决方案:

#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

其他人提到了一些设计良好的第三方库,您可能会求助于它们。但是,如果使用 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);
}

Note: The code compiles with any compiler supporing C++11, and it keeps consistency with design of the standard library:

  1. 元组不需要是 std::tuple,而可以是任何支持 std::getstd::tuple_size的东西; 特别是,可以使用 std::arraystd::pair;

  2. 元组可以是引用类型或 cv 限定的;

  3. 它具有与 std::for_each类似的行为,并返回输入 UnaryFunction;

  4. 对于 C + + 14(或最新版本)用户,可以用简化版 std::enable_if_t<T>std::decay_t<T>代替 typename std::enable_if<T>::typetypename std::decay<T>::type;

  5. 对于 C + + 17(或最新版本)用户,可以用简化版 std::tuple_size_v<T>代替 std::tuple_size<T>::value

  6. 对于 C + + 20(或最新版本)用户,可以使用 Concepts实现 SFINAE特性。

在 C + + 17中,可以使用 std::apply折叠式表情:

std::apply([](auto&&... args) {((/* args.dosomething() */), ...);}, the_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);
}

[ Coliru 在线示例]

该解决方案解决了 阿拉根先生的回答中的评价顺序问题。

在 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,所以(我认为)它对编译器更加友好。

C + + 为此引入了 扩展报表。他们最初是在 C + + 20的轨道上,但由于缺乏时间的语言措辞审查(见 给你给你) ,勉强错过了削减。

目前公认的语法(见上面的链接)是:

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

使用 constexprif 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);
}
}

另一种选择是为元组实现迭代器。这样做的好处是,您可以使用标准库和基于范围的 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(),也支持只读访问。

通过扩展@Sty痘的应答,我们可以使他们的解决方案更加通用(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));
}
}

然后,我们需要一个策略来访问每种类型。

让我们从一些助手开始(前两个助手来自 cpferences) :

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

为了完整起见,以下是我的 BarFoo:

struct Foo {
void foo() {std::cout << "Foo" << i++ << std::endl;}
int i = 0;
};
struct Bar {
void bar() const {std::cout << "Bar" << std::endl;}
};

有很多很好的答案,但出于某种原因,大多数答案都没有考虑返回将 f 应用于 tuple 的结果..。 还是我忽略了它? 不管怎样,还有另一种方法可以做到这一点:

用风格进行 Foreach (有争议)

auto t = std::make_tuple(1, "two", 3.f);
t | foreach([](auto v){ std::cout << v << " "; });

从那以后:

    auto t = std::make_tuple(1, "two", 3.f);
auto sizes = t | foreach([](auto v) {
return sizeof(v);
});
sizes | foreach([](auto v) {
std::cout << v;
});

实现(相当简单的一个)

编辑: 它变得有点混乱。

我不会在这里包括一些元编程样板,因为它肯定会使事情变得不那么可读,而且,我相信这些问题已经在堆栈溢出的某个地方得到了回答。 如果您感到懒惰,可以随意查看我的 Github Repo来实现这两个功能

#include <utility>




// Optional includes, if you don't want to implement it by hand or google it
// you can find it in the repo (link below)
#include "typesystem/typelist.hpp"
// used to check if all return types are void,
// making it a special case
// (and, alas, not using constexpr-if
//    for the sake of being compatible with C++14...)




template <bool Cond, typename T, typename F>
using select = typename std::conditional<Cond, T, F>::type;




template <typename F>
struct elementwise_apply {
F f;
};


template <typename F>
constexpr auto foreach(F && f) -> elementwise_apply<F> { return {std::forward<F>(f)}; }




template <typename R>
struct tuple_map {
template <typename F, typename T, size_t... Is>
static constexpr decltype(auto) impl(std::index_sequence<Is...>, F && f, T&& tuple) {
return R{ std::forward<F>(f)( std::get<Is>(tuple) )... };
}
};


template<>
struct tuple_map<void> {
template <typename F, typename T, size_t... Is>
static constexpr void impl(std::index_sequence<Is...>, F && f, T&& tuple) {
[[maybe_unused]] std::initializer_list<int> _ {((void)std::forward<F>(f)( std::get<Is>(tuple) ), 0)... };
}
};


template <typename F, typename... Ts>
constexpr decltype(auto) operator| (std::tuple<Ts...> & t, fmap<F> && op) {
constexpr bool all_void = core::Types<decltype( std::move(op).f(std::declval<Ts&>()) )...>.all( core::is_void );
using R = meta::select<all_void, void, std::tuple<decltype(std::move(op).f(std::declval<Ts&>()))...>>;
return tuple_map<R>::impl(std::make_index_sequence<sizeof...(Ts)>{}, std::move(op).f, t);
}


template <typename F, typename... Ts>
constexpr decltype(auto) operator| (std::tuple<Ts...> const& t, fmap<F> && op) {
constexpr bool all_void = check if all "decltype( std::move(op).f(std::declval<Ts>()) )..." types are void, since then it's a special case
// e.g. core::Types<decltype( std::move(op).f(std::declval<Ts>()) )...>.all( core::is_void );
using R = meta::select<all_void, void, std::tuple<decltype(std::move(op).f(std::declval<Ts const&>()))...>>;
return tuple_map<R>::impl(std::make_index_sequence<sizeof...(Ts)>{}, std::move(op).f, t);
}


template <typename F, typename... Ts>
constexpr decltype(auto) operator| (std::tuple<Ts...> && t, fmap<F> && op) {
constexpr bool all_void = core::Types<decltype( std::move(op).f(std::declval<Ts&&>()) )...>.all( core::is_void );
using R = meta::select<all_void, void, std::tuple<decltype(std::move(op).f(std::declval<Ts&&>()))...>>;
return tuple_map<R>::impl(std::make_index_sequence<sizeof...(Ts)>{}, std::move(op).f, std::move(t));
}

是啊,如果我们用 C + + 17就更好了

这也是 std: : move object 的成员的一个示例,对于这个示例,我最好引用这个漂亮的简短 文章

附注: 如果您卡住了,检查所有“ dectype (std: : move (op) . f (std: : decval ())) ...”类型是否为 void 你可以找到一些元编程库,或者,如果这些库看起来太难掌握(其中一些可能是由于一些疯狂的元编程技巧) ,你知道在哪里可以找到“一个 href =”https://github.com/AVasK/core“ rel =”nofollow noReferrer“ >

template <typename F, typename T>
static constexpr size_t
foreach_in_tuple(std::tuple<T> & tuple, F && do_, size_t index_ = 0)
{
do_(tuple, index_);
return index_;
}


template <typename F, typename T, typename U, typename... Types>
static constexpr size_t
foreach_in_tuple(std::tuple<T,U,Types...> & tuple, F && do_, size_t index_ = 0)
{
if(!do_(tuple, index_))
return index_;
auto & next_tuple = reinterpret_cast<std::tuple<U,Types...> &>(tuple);
return foreach_in_tuple(next_tuple, std::forward<F>(do_), index_+1);
}




int main()
{
using namespace std;
auto tup = make_tuple(1, 2.3f, 'G', "hello");


foreach_in_tuple(tup, [](auto & tuple, size_t i)
{
auto & value = std::get<0>(tuple);
std::cout << i << " " << value << std::endl;
// if(i >= 2) return false; // break;
return true; // continue
});
}