итерация по кортежу


Как я могу перебирать кортеж (используя C++11)? Я попробовал следующее, Но это не работает:

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

Ошибка 1: извините, нереализовано: не удается развернуть ' прослушиватель ...- в список аргументов фиксированной длины.
Ошибка 2: я не могу появиться в константное выражение.

Итак, как мне правильно перебирать элементы кортежа?

12 66

12 ответов:

импульс.Fusion - это возможность:

непроверенные пример:

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

tuple<....> t = ...;
boost::fusion::for_each(t, DoSomething());

у меня есть ответ, основанный на итерация по кортежу:

#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 представляют что-то с соответствующими перегрузками для каждого типа Кортеж может содержать. Это работает лучше всего, если вы знаете, что все элементы Кортежа будут иметь общий базовый класс или что-то подобное.

Использовать Boost.Хана и родовые лямбды:

#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

В C++17 вы можете сделать это:

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

Это уже работает в Clang++ 3.9, используя std::experimental:: apply.

вам нужно использовать шаблон метапрограммирования, здесь показано с 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() в качестве функции вариационного шаблона вместо этого.

сначала определите некоторые помощники индекса:

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

Если вы хотите использовать 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;
}

власть шаблонов с переменным количеством аргументов!

кортеж 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 что имеет другие преимущества (например, уже предоставленные операторы ввода-вывода). Хотя есть и обратная сторона boost::tuple С gcc-он еще не принимает вариативные шаблоны, но это может быть уже исправлено, поскольку у меня нет последней версии boost, установленной на моей машине.

Я, возможно, пропустили этот поезд, но это будет здесь в будущем.
Вот моя конструкция, основанная на этом ответ и этот суть:

#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 итерации по элементам кортежа с помощью только стандартной библиотеки:

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

std::tuple<int, char> items;
for_each(items, [](const auto& item) {
    std::cout << item << "\n";
});

в 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::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's std::make_index_sequence может быть реализована для C++11.