Como você pode iterar sobre os elementos de uma tupla de std ::?
-
20-09-2019 - |
Pergunta
Como posso iterar sobre uma tupla (usando C ++ 11)? Eu tentei o seguinte:
for(int i=0; i<std::tuple_size<T...>::value; ++i)
std::get<i>(my_tuple).do_sth();
Mas isso não funciona:
Erro 1: Desculpe, não implementado: não é possível expandir 'ouvinte ...' em uma lista de argumentos de comprimento fixo.
Erro 2: Não posso aparecer em uma expressão constante.
Então, como faço para iterar corretamente os elementos de uma tupla?
Solução
Boost.fusion é uma possibilidade:
Exemplo não testado:
struct DoSomething
{
template<typename T>
void operator()(T& t) const
{
t.do_sth();
}
};
tuple<....> t = ...;
boost::fusion::for_each(t, DoSomething());
Outras dicas
Eu tenho uma resposta baseada em Iterando sobre uma tupla:
#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);
}
A idéia usual é usar a recursão de tempo de compilação. De fato, essa idéia é usada para criar um tipo de impressão que seja seguro, conforme observado nos papéis originais da tupla.
Isso pode ser facilmente generalizado em um for_each
Para tuplas:
#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);
}
Embora isso exija algum esforço para ter FuncT
Represente algo com as sobrecargas apropriadas para todos os tipos que a tupla pode conter. Isso funciona melhor se você souber que todos os elementos da tupla compartilharão uma classe base comum ou algo semelhante.
Em C ++ 17, você pode usar std::apply
com Dobre a expressão:
std::apply([](auto&&... args) {((/* args.dosomething() */), ...);}, the_tuple);
Um exemplo completo para imprimir uma tupla:
#include <tuple>
#include <iostream>
int main()
{
std::tuple t{42, 'a', 4.2}; // Another C++17 feature: class template argument deduction
std::apply([](auto&&... args) {((std::cout << args << '\n'), ...);}, t);
}
Esta solução resolve a questão da ordem de avaliação em M. ALAGGAN Resposta.
Use Boost.hana e Lambdas genéricos:
#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;
}
Em C ++ 17, você pode fazer isso:
std::apply([](auto ...x){std::make_tuple(x.do_something()...);} , the_tuple);
Isso já funciona no Clang ++ 3.9, usando o STD :: Experimental :: Aplicar.
Você precisa usar o modelo metaprograma, aqui mostrado com boost.tuple:
#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;
}
Em C ++ 0x, você pode escrever print_tuple()
como uma função de modelo variável.
C ++ está introduzindo declarações de expansão para esse fim. Eles estavam originalmente no caminho certo para C ++ 20, mas perdeu por pouco o corte devido à falta de tempo para revisão de redação de idiomas (ver aqui e aqui).
A sintaxe atualmente acordada (veja os links acima) é:
{
auto tup = std::make_tuple(0, 'a', 3.14);
template for (auto elem : tup)
std::cout << elem << std::endl;
}
Primeiro defina alguns ajudantes de índice:
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...> {};
Com sua função, você gostaria de aplicar em cada elemento de tupla:
template <typename T>
/* ... */ foo(T t) { /* ... */ }
você pode escrever:
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)>());
}
Ou se foo
retorna void
, usar
std::tie((foo(std::get<I>(ts)), 1) ... );
Nota: em C ++ 14 make_index_sequence
já está definido (http://en.cppreference.com/w/cpp/utility/integer_sequence).
Se você precisar de uma ordem de avaliação da esquerda para a direita, considere algo assim:
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)>());
}
Uma maneira mais simples, intuitiva e amiga do compilador de fazer isso em C ++ 17, usando if constexpr
:
// prints every element of a tuple
template<size_t I = 0, typename... Tp>
void print(std::tuple<Tp...>& t) {
std::cout << std::get<I>(t) << " ";
// do things
if constexpr(I+1 != sizeof...(Tp))
print<I+1>(t);
}
Esta é a recursão de tempo de compilação, semelhante à apresentada pelo @EMSR. Mas isso não usa sfinae, então (eu acho) é mais amigável ao compilador.
Se você deseja usar o STD :: Tuple e possui compilador C ++, que suporta modelos variádicos, tente o código abaixo (testado com G ++ 4.5). Esta deve ser a resposta para sua pergunta.
#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 é outra opção, mas requer seu próprio tipo de tupla: boost :: fusion :: tupla. Vamos nos manter melhor no padrão! Aqui está um teste:
#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;
}
O poder dos modelos variádicos!
Aqui está uma maneira fácil do C ++ 17 de iterar os itens de tupla com apenas biblioteca padrão:
#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)...);
}
}
Exemplo:
#include <iostream>
int main()
{
std::tuple<int, char> items{1, 'a'};
for_each(items, [](const auto& item) {
std::cout << item << "\n";
});
}
Resultado:
1
a
Isso pode ser estendido para quebrar condicionalmente o loop, caso o chamável retorne um valor (mas ainda trabalhe com callable que não retornam um valor atribuível de bool, por exemplo, vazio):
#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)
{
if constexpr (std::is_assignable_v<bool&, std::invoke_result_t<TCallable&&, TArgs&&..., decltype(std::get<Index>(tuple))>>)
{
if (!std::invoke(callable, args..., std::get<Index>(tuple)))
return;
}
else
{
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)...);
}
}
Exemplo:
#include <iostream>
int main()
{
std::tuple<int, char> items{ 1, 'a' };
for_each(items, [](const auto& item) {
std::cout << item << "\n";
});
std::cout << "---\n";
for_each(items, [](const auto& item) {
std::cout << item << "\n";
return false;
});
}
Resultado:
1
a
---
1
A tupla do Boost fornece funções auxiliares get_head()
e get_tail()
Portanto, suas funções ajudantes podem ser assim:
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()); }
Como descrito aqui http://www.boost.org/doc/libs/1_34_0/libs/tuple/doc/tuple_advanced_interface.html
com std::tuple
deve ser semelhante.
Na verdade, infelizmente std::tuple
não parece fornecer essa interface, portanto, os métodos sugeridos antes devem funcionar, ou você precisaria mudar para boost::tuple
que tem outros benefícios (como os operadores de E / S já fornecidos). Embora exista o lado negativo de boost::tuple
Com o GCC - ele ainda não aceita modelos variádicos, mas isso já pode ser corrigido, pois não tenho a versão mais recente do Boost instalada na minha máquina.
Eu poderia ter perdido este trem, mas isso estará aqui para referência futura.
Aqui está minha construção com base nisso responda E nisso essência:
#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){}
};
Você então usa o seguinte:
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);
/* ... */
}
Pode haver espaço para melhorias.
De acordo com o código do OP, se tornaria o seguinte:
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);
No MSVC STL, há uma função _for_each_tuple_element (não documentada):
#include <tuple>
// ...
std::tuple<int, char, float> values{};
std::_For_each_tuple_element(values, [](auto&& value)
{
// process 'value'
});
De todas as respostas que já vi aqui, aqui e aqui, Eu gostei @sigidagiA maneira de itera melhor. Infelizmente, sua resposta é muito detalhada que, na minha opinião, obscurece a clareza inerente.
Esta é a minha versão da solução dele, que é mais concisa e trabalha com std::tuple
, std::pair
e 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>()
);
}
Demo: Coliru
C ++ 14's std::make_index_sequence
pode ser implementado Para C ++ 11.
Usando constexpr
e if constexpr
(C ++ 17) Isso é bastante simples e direto:
template <std::size_t I = 0, typename ... Ts>
void print(std::tuple<Ts...> tup) {
if constexpr (I == sizeof...(Ts)) {
return;
} else {
std::cout << std::get<I>(tup) << ' ';
print<I+1>(tup);
}
}
Eu tropecei no mesmo problema para iterar sobre uma tupla de objetos de função, então aqui está mais uma solução:
#include <tuple>
#include <iostream>
// Function objects
class A
{
public:
inline void operator()() const { std::cout << "A\n"; };
};
class B
{
public:
inline void operator()() const { std::cout << "B\n"; };
};
class C
{
public:
inline void operator()() const { std::cout << "C\n"; };
};
class D
{
public:
inline void operator()() const { std::cout << "D\n"; };
};
// Call iterator using recursion.
template<typename Fobjects, int N = 0>
struct call_functors
{
static void apply(Fobjects const& funcs)
{
std::get<N>(funcs)();
// Choose either the stopper or descend further,
// depending if N + 1 < size of the tuple.
using caller = std::conditional_t
<
N + 1 < std::tuple_size_v<Fobjects>,
call_functors<Fobjects, N + 1>,
call_functors<Fobjects, -1>
>;
caller::apply(funcs);
}
};
// Stopper.
template<typename Fobjects>
struct call_functors<Fobjects, -1>
{
static void apply(Fobjects const& funcs)
{
}
};
// Call dispatch function.
template<typename Fobjects>
void call(Fobjects const& funcs)
{
call_functors<Fobjects>::apply(funcs);
};
using namespace std;
int main()
{
using Tuple = tuple<A,B,C,D>;
Tuple functors = {A{}, B{}, C{}, D{}};
call(functors);
return 0;
}
Resultado:
A
B
C
D
Outros mencionaram algumas bibliotecas de terceiros bem projetadas às quais você pode recorrer. No entanto, se você estiver usando C ++ sem essas bibliotecas de terceiros, o código a seguir poderá ajudar.
namespace detail {
template <class Tuple, std::size_t I, class = void>
struct for_each_in_tuple_helper {
template <class UnaryFunction>
static void apply(Tuple&& tp, UnaryFunction& f) {
f(std::get<I>(std::forward<Tuple>(tp)));
for_each_in_tuple_helper<Tuple, I + 1u>::apply(std::forward<Tuple>(tp), f);
}
};
template <class Tuple, std::size_t I>
struct for_each_in_tuple_helper<Tuple, I, typename std::enable_if<
I == std::tuple_size<typename std::decay<Tuple>::type>::value>::type> {
template <class UnaryFunction>
static void apply(Tuple&&, UnaryFunction&) {}
};
} // namespace detail
template <class Tuple, class UnaryFunction>
UnaryFunction for_each_in_tuple(Tuple&& tp, UnaryFunction f) {
detail::for_each_in_tuple_helper<Tuple, 0u>
::apply(std::forward<Tuple>(tp), f);
return std::move(f);
}
Nota: O código compila com qualquer compilador que suponha C ++ 11 e mantém a consistência com o design da biblioteca padrão:
A tupla não precisa ser
std::tuple
, e, em vez disso, pode ser qualquer coisa que apóiestd::get
estd::tuple_size
; em particular,std::array
estd::pair
pode ser usado;A tupla pode ser um tipo de referência ou qualificado por CV;
Tem comportamento semelhante ao
std::for_each
, e retorna a entradaUnaryFunction
;Para usuários C ++ 14 (ou versão do lastro),
typename std::enable_if<T>::type
etypename std::decay<T>::type
pode ser substituído por sua versão simplificada,std::enable_if_t<T>
estd::decay_t<T>
;Para C ++ 17 (ou versão do lastro), usuários,
std::tuple_size<T>::value
pode ser substituído por sua versão simplificada,std::tuple_size_v<T>
.Para usuários de C ++ 20 (ou versão do lastro), o
SFINAE
o recurso pode ser implementado com oConcepts
.