Как перебирать std:: tuple в С++ 11
Я сделал следующий кортеж:
Я хочу знать, как мне перебирать его? Существует tupl_size()
, но, читая документацию, я не понял, как ее использовать. Также у меня есть поиск SO, но вопросы, кажется, вокруг Boost::tuple
.
auto some = make_tuple("I am good", 255, 2.1);
Ответы
Ответ 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
также может быть выведен как ссылочный тип.