Подтвердить что ты не робот

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

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

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

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

Ответ 1

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

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

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

Мы можем inline 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, скрывается, делая это inline, на мой взгляд.

index_sequence (и поддерживающие шаблоны) - это функция С++ 14, которая может быть написана на С++ 11. Найти такую ​​реализацию при переполнении стека легко. Текущий топ google hit - достойная реализация глубины 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.

В С++ 1z мы также можем использовать std::apply с объектом функции for_each_arg, чтобы избавиться от индексации индекса.

Ответ 2

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

Ответ 3

Вот похожее и более подробное решение, чем ранее принятое решение, данное 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>(std::forward<TupleType>(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


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

Объяснение:

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

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

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




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

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