您如何遍历std :: tuple的元素?


112

我如何遍历一个元组(使用C ++ 11)?我尝试了以下方法:

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

但这不起作用:

错误1:抱歉,未实现:无法将“侦听器...”扩展为固定长度的参数列表。
错误2:我无法出现在常量表达式中。

那么,如何正确遍历元组的元素?


2
请问,您是如何在C ++ 0x中编译的?据我所知,它尚未发布或准备就绪。
Burkhard)

5
自版本4.3起,g ++包含对某些C ++ 0X功能的实验性支持,包括可变参数模板。其他编译器也做同样的事情(具有不同的功能集,如果您想在生产中使用它们,那么您将回到90年代,并提供对前沿技术的广泛支持)
AProgrammer,2009年

我使用的g ++版本4.4和std = c ++ 0x

9
这个问题需要C ++ 11更新。
13年

2
@Omnifarious现在,它需要C ++ 14更新
pepper_chico 2014年

Answers:


26

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上)?有人提示吗?
sehe 2013年

@ViktorSehr发现了问题:错误/遗漏导致融合的行为取决于包含的顺序,有关更多详细信息,请参见Boost票证#8418
13年

需要使用boost :: fusion :: tuple而不是std :: tuple来使它工作。
Marcin

在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]的声明失败************(升压:: MPL :: not_ <泼尼松> :: ************
侯赛因

129

我有一个基于遍历元组的答案:

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

通常的想法是使用编译时间递归。实际上,这种想法被用于制造一种打印安全的类型,如原始元组论文中所述。

可以很容易地将其概括为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);
  }

尽管这需要付出一些努力才能FuncT用元组可能包含的每种类型来表示具有适当重载的内容。如果您知道所有元组元素将共享一个公共基类或类似的东西,那么这将是最好的方法。


5
感谢您提供的简单示例。对于希望了解其工作原理的C ++初学者,请参阅SFINAEenable_if文档
Faheem Mitha 2012年

可以很容易地将其概括为通用的for_each。实际上,我自己做了。:-)我认为,如果这个答案已经被普遍化的话,它将更加有用。
全方位

4
在那里,我添加了泛化,因为我实际上需要一个泛化,并且我认为它对其他人很有用。
13年

2
注意:您可能还需要带有const std::tuple<Tp...>&..的版本。如果您不想在迭代时修改元组,则这些const版本就足够了。
致命吉他

2
不像编写的那样。您可以制作一个索引翻转的版本-从I = sizeof ...(Tp)开始并递减计数。然后显式提供最大数量的args。您还可以创建一个破坏了标签类型的版本,例如break_t。然后,当您要停止打印时,可以将这种标签类型的对象放入元组中。或者,您可以提供停止类型作为模板参数。显然,您无法在运行时中断。
emsr 2014年

54

在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在线示例]

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


1
您能解释一下这里发生了((std::cout << args << '\n'), ...);什么吗?调用lambda时,将tuple-elements解压缩为args,但是用双括号括起来又是怎么回事?
helmesjo

4
@helmesjo它((std::cout << arg1 << '\n'), (std::cout << arg2 << '\n'), (std::cout << arg3 << '\n'))在此处扩展为逗号表达式。
xskxzr

请注意,如果您要执行在逗号表达式中不合法的事情(例如,声明变量和块),则可以将所有内容放入方法中,并从折叠逗号表达式中简单地调用它。
Miral

24

在C ++ 17中,您可以执行以下操作:

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

使用std :: experimental :: apply在Clang ++ 3.9中已经可以使用。


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

21

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


4
请请不要走using namespace boost::fusion(尤其是与一起using namespace std)。现在没有办法知道那for_eachstd::for_each还是boost::fusion::for_each
Bulletmagnet

3
@Bulletmagnet这样做是为了简洁,ADL可以毫无问题地处理它。此外,它还具有本地功能。
pepper_chico

16

为此,C ++引入了扩展语句。他们本来可以使用C ++ 20,但是由于缺少时间进行语言措辞审查而几乎错过了晋级(请参阅此处此处))。

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

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

15

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

您需要使用模板元编程,此处显示为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()改为编写可变参数模板函数。


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

使用您的函数,您想应用到每个元组元素:

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)>());
}

1
调用前应将返回值转换为foo,以避免可能的病理操作符过载。voidoperator,
Yakk-Adam Nevraumont 2014年

7

这是一种仅使用标准库即可遍历元组项的简单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> items{1, '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

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

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

可变参数模板的功能!


我尝试了您的第一个解决方案,但此功能无法成对使用。知道为什么吗?模板<typename T,typename U> void addt(pair <T,U> p){cout << p.first + p.second << endl; } int main(int argc,char * argv []){cout <<“你好。” << endl; for_each(make_tuple(2,3,4),[](int i){cout << i << endl;}); for_each(make_tuple(make_pair(1,2),make_pair(3,4)),添加); 返回0; }
user2023370'1

可惜的是,这个答案如此冗长,因为我认为迭代(for_each_impl)的方式是我所看到的所有解决方案中最优雅的一种。
条既纳

3

在MSVC STL中,有一个_For_each_tuple_element函数(未记录):

#include <tuple>

// ...

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

2

其他人提到了一些设计良好的第三方库,您可能会想到这些库。但是,如果使用的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的任何编译器进行编译,并且可以与标准库的设计保持一致:

  1. 元组不必是std::tuple,而可以是任何支持std::getand 的元组std::tuple_size;特别地,std::arraystd::pair可以使用;

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

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

  4. 用于C ++ 14(或拉斯特版本)用户,typename std::enable_if<T>::typetypename std::decay<T>::type可以用它们的简化版本,被替换std::enable_if_t<T>std::decay_t<T>;

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

  6. 对于C ++ 20(或更高版本)的用户,SFINAE可以使用来实现此功能Concepts


2

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

1

我可能错过了这趟火车,但这将在这里供以后参考。
这是我根据这个答案要点构造的:

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

1

在这里,这里这里看到的所有答案中,我喜欢@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>()
    );
}

演示:coliru

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


0

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运算符)。尽管boost::tuplegcc 有缺点-它尚不接受可变参数模板,但由于我的计算机上未安装最新版本的boost,因此该问题可能已得到解决。


0

我偶然发现了一个遍历功能对象元组的相同问题,因此这里有一个解决方案:

#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

0

另一种选择是为元组实现迭代器。这样的好处是您可以使用标准库提供的各种算法以及基于范围的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()


0

扩展@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

为了完整起见,这是我的BarFoo

struct Foo {
    void foo() {std::cout << "Foo" << i++ << std::endl;}
    int i = 0;
};
struct Bar {
    void bar() const {std::cout << "Bar" << std::endl;}
};
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.