Как перебрать 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::pair
s. Он также перенаправляет категорию значений 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; });
Общая идея: как и в решении ТС, начать с индекса 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
также может быть выведен как ссылочный тип.