Как перебрать std::tuple в C++ 11

Я сделал следующий кортеж:

Я хочу знать, как мне это повторить? Есть tupl_size()Но, читая документацию, я не понял, как ее использовать. Также у меня есть поиск ТАК, но вопросы вроде бы есть Boost::tuple,

auto some = make_tuple("I am good", 255, 2.1);

3 ответа

Решение

Вот попытка разбить итерацию по кортежу на составные части.

Во-первых, функция, представляющая выполнение последовательности операций по порядку. Обратите внимание, что многие компиляторы находят это сложным для понимания, несмотря на то, что, насколько я могу судить, это 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::pairs. Он также перенаправляет категорию значений r/l упомянутого объекта вниз к объекту функции, который он вызывает. Также обратите внимание, что если у вас есть бесплатная функция get<N> на ваш пользовательский тип, и вы переопределяете get_indexes, выше for_each будет работать на ваш пользовательский тип.

Как указано, do_in_order в то время как аккуратность не поддерживается многими компиляторами, так как им не нравится лямбда с нерасширенными пакетами параметров, расширяемыми в пакеты параметров.

Мы можем встроить 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++14, которая может быть написана на C++11. Найти такую ​​реализацию по переполнению стека легко. Текущий топ-рейтинг Google - это достойная реализация глубины O(lg(n)), которая, если я правильно прочитал комментарии, может стать основой хотя бы для одной итерации фактического 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 объект функции, чтобы покончить с изменением индекса.

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 доступно много). Полиморфные лямбды также являются C++14, но могут быть заменены написанным на заказ функтором.

Вот похожее и более подробное решение, чем ранее принятое решение, данное 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; });

DEMO


Общая идея: как и в решении ТС, начать с индекса I=0 и подойти к размеру кортежа. Однако здесь это делается не для вариационного расширения, а по одному.

Объяснение:

  • Первая перегрузка for_each называется если I равен размеру кортежа. Функция тогда просто ничего не делает и таким образом завершает рекурсию.

  • Вторая перегрузка вызывает функцию с аргументом std::get<I>(t) и увеличивает индекс на единицу. Класс std::integral_constant необходим для того, чтобы решить значение I во время компиляции. std::enable_if Материал SFINAE используется, чтобы помочь компилятору отделить эту перегрузку от предыдущей и вызывать эту перегрузку, только если I меньше, чем размер кортежа (в Coliru это необходимо, тогда как в Visual Studio он работает без).

  • Третий начинает рекурсию с I=0, Это перегрузка, которая обычно вызывается извне.




РЕДАКТИРОВАТЬ: я также включил идею, упомянутую Yakk для дополнительной поддержки std::array а также std::pair с помощью общего параметра шаблона TupleType вместо того, который специализируется на std::tuple<Ts ...>,

Как TupleType type должен быть выведен и является такой "универсальной ссылкой", это также имеет то преимущество, что каждый получает идеальную пересылку бесплатно. Недостатком является то, что нужно использовать другое косвенное обращение через typename std::remove_reference<TupleType>::type, как TupleType также может быть выведен как ссылочный тип.

Другие вопросы по тегам