Como você pode iterar sobre os elementos de uma std :: tuple?


112

Como posso iterar em uma tupla (usando C ++ 11)? 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 'Listener ...' 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?


2
Posso perguntar como você compila em C ++ 0x? Não está lançado nem pronto, tanto quanto eu sei.
Burkhard

5
g ++ contém suporte experimental de alguns recursos C ++ 0X, incluindo modelos variadic, desde a versão 4.3. Outros compiladores fazem o mesmo (com conjuntos de recursos diferentes, se você quiser usá-los na produção, você está de volta aos anos 90 com uma ampla variação de suporte para coisas de ponta)
AProgrammer

Estou usando o g ++ versão 4.4 com std = c ++ 0x

9
Esta questão precisa de uma atualização do C ++ 11.
Onifário

2
@Omnifarious agora, ele precisa de uma atualização do C ++ 14
pepper_chico

Respostas:


26

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());

@ViktorSehr AFAICT não (pelo menos no GCC 4.7.2)? Alguém com uma dica?
ver

@ViktorSehr Encontrou o problema: um bug / omissão faz com que o comportamento da fusão dependa da ordem dos includes, consulte Boost ticket # 8418 para mais detalhes
ver

precisa usar boost :: fusion :: tuple ao invés de std :: tuple para que isso funcione.
Marcin

No GCC 8.1 / mingw-64, recebo dois avisos para o uso de boost :: fusion :: for_each com expressões lambda std: boost / mpl / assert.hpp: 188: 21: aviso: parênteses desnecessários na declaração de 'assert_arg' [-Wparentheses] falhou ************ (Pred :: ************ boost / mpl / assert.hpp: 193: 21: aviso: parênteses desnecessários entre declaração de 'assert_not_arg' [-Wparentheses] falhou ************ (boost :: mpl :: not_ <Pred> :: ************
Hossein

129

Eu tenho uma resposta baseada na iteração 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 recursão em tempo de compilação. Na verdade, essa ideia é usada para tornar um printf seguro para tipos, conforme observado nos documentos de tupla originais.

Isso pode ser facilmente generalizado em for_eachtuplas for:

#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 requeira algum esforço para FuncTrepresentar algo com as sobrecargas apropriadas para cada tipo que a tupla possa conter. Isso funciona melhor se você souber que todos os elementos da tupla compartilharão uma classe base comum ou algo semelhante.


5
Obrigado pelo bom exemplo simples. Para iniciantes em C ++ que buscam informações sobre como isso funciona, consulte SFINAE e a enable_ifdocumentação .
Faheem Mitha

Isso poderia ser facilmente generalizado para ser um genérico for_each. Na verdade, eu mesmo fiz. :-) Acho que esta resposta seria mais útil se já fosse generalizada.
Onifário

4
Lá, adicionei a generalização porque na verdade precisava de uma e acho que seria útil para os outros verem.
Onifário

2
Nota: Você também pode precisar de versões com const std::tuple<Tp...>&.. Se você não pretende modificar tuplas durante a iteração, essas constversões serão suficientes.
guitarra letal de

2
Não como está escrito .. Você poderia fazer uma versão com a indexação invertida - comece em I = sizeof ... (Tp) e faça uma contagem regressiva. Em seguida, forneça um número máximo de argumentos explicitamente. Você também pode fazer uma versão que quebra em um tipo de tag, digamos break_t. Em seguida, você colocaria um objeto desse tipo de tag em sua tupla quando quisesse parar de imprimir. Ou você pode fornecer um tipo de parada como um parâmetro de modelo. Obviamente você não pode quebrar em tempo de execução.
emsr

55

Em C ++ 17, você pode usar std::applycom expressão de dobra :

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);
}

[Exemplo online no Coliru]

Esta solução resolve o problema da ordem de avaliação na resposta de M. Alaggan .


1
Você poderia explicar o que está acontecendo aqui ((std::cout << args << '\n'), ...);:? O lambda é invocado uma vez com os elementos da tupla descompactados como args, mas o que há com os parênteses duplos?
helmesjo

4
@helmesjo Ele se expande para uma expressão de vírgula ((std::cout << arg1 << '\n'), (std::cout << arg2 << '\n'), (std::cout << arg3 << '\n'))aqui.
xskxzr

Observe que no caso de você querer fazer coisas que não são legais em uma expressão de vírgula (como declarar variáveis ​​e blocos), você pode colocar tudo isso em um método e simplesmente chamá-lo de dentro da expressão de vírgula dobrada.
Miral

24

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 std :: experimental :: apply.


4
Isso não leva à iteração - ou seja, chamadas de do_something()- ocorrendo em uma ordem não especificada, porque o pacote de parâmetros é expandido dentro de uma chamada de função (), em que os argumentos têm uma ordem não especificada? Isso pode ser muito significativo; Eu imagino que a maioria das pessoas esperaria que a ordenação ocorresse na mesma ordem que os membros, ou seja, como os índices std::get<>(). AFAIK, para ter pedido garantido em casos como este, a expansão deve ser feita dentro {braces}. Estou errado? Esta resposta enfatiza essa ordem: stackoverflow.com/a/16387374/2757035
underscore_d

21

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;
}

http://coliru.stacked-crooked.com/a/27b3691f55caf271


4
Por favor, por favor, não vá using namespace boost::fusion(especialmente junto com using namespace std). Agora não há como saber se for_eaché std::for_eachouboost::fusion::for_each
Bulletmagnet

3
@Bulletmagnet isso foi feito para concisão aqui e ADL pode lidar com isso sem problemas. Além disso, também é função local.
pepper_chico

16

C ++ está introduzindo instruções de expansão para esse propósito. Eles estavam originalmente no caminho certo para o C ++ 20, mas por pouco perderam o corte devido à falta de tempo para revisão da redação do idioma (veja aqui e aqui ).

A sintaxe atualmente aceita (veja os links acima) é:

{
    auto tup = std::make_tuple(0, 'a', 3.14);
    template for (auto elem : tup)
        std::cout << elem << std::endl;
}

15

Uma maneira mais simples, intuitiva e amigável 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 em tempo de compilação, semelhante à apresentada por @emsr. Mas isso não usa SFINAE, então (eu acho) é mais amigável ao compilador.


8

Você precisa usar a metaprogramação de modelo, mostrada aqui 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.


8

Primeiro defina alguns auxiliares 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 da 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 fooretornar void, use

std::tie((foo(std::get<I>(ts)), 1) ... );

Nota: Em C ++ 14 make_index_sequencejá está definido ( http://en.cppreference.com/w/cpp/utility/integer_sequence ).

Se você precisar de um pedido 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)>());
}

1
Deve converter o valor de retorno de foopara voidantes de invocar operator,para evitar uma possível sobrecarga do operador patológico.
Yakk - Adam Nevraumont

7

Esta é uma maneira fácil em C ++ 17 de iterar itens de tupla apenas com a 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 no caso de o chamável retornar um valor (mas ainda funcionar com os chamáveis ​​que não retornam um valor atribuível bool, por exemplo, void):

#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

5

Se você quiser usar std :: tuple e tiver um compilador C ++ que suporte modelos variadic, tente o código abaixo (testado com g ++ 4.5). Esta deve ser a resposta à 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 :: tuple. É melhor seguir o 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 variadic!


Tentei sua primeira solução, mas ela falha com essa função em pares. Alguma ideia do porquê? Template <nome do tipo T, nome do tipo U> void addt (par <T, U> p) {cout << p.first + p.segundo << endl; } int main (int argc, char * argv []) {cout << "Olá". << endl; for_each (make_tuple (2,3,4), [] (int i) {cout << i << endl;}); for_each (make_tuple (make_pair (1,2), make_pair (3,4)), addt); return 0; }
user2023370

É uma pena que essa resposta tenha sido escrita de forma tão prolixa, porque acho que a maneira de iterar (for_each_impl) é a mais elegante de todas as soluções que já vi.
joki

3

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'
});

2

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 pode 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);
}

Observação: o código é compilado com qualquer compilador compatível com C ++ 11 e mantém a consistência com o design da biblioteca padrão:

  1. A tupla não precisa ser std::tuplee, em vez disso, pode ser qualquer coisa que suporte std::gete std::tuple_size; em particular, std::arraye std::pairpode ser usado;

  2. A tupla pode ser um tipo de referência ou qualificada pelo CV;

  3. Ele tem um comportamento semelhante ao std::for_eache retorna a entrada UnaryFunction;

  4. Para usuários de C ++ 14 (ou versão mais recente), typename std::enable_if<T>::typee typename std::decay<T>::typepode ser substituído por sua versão simplificada std::enable_if_t<T>e std::decay_t<T>;

  5. Para usuários de C ++ 17 (ou versão mais recente), std::tuple_size<T>::valuepode ser substituído por sua versão simplificada std::tuple_size_v<T>,.

  6. Para usuários do C ++ 20 (ou versão mais recente), o SFINAErecurso pode ser implementado com o Concepts.


2

Usar constexpre if constexpr(C ++ 17) é 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);
  }
}

1

Posso ter perdido este trem, mas ele estará aqui para referência futura.
Aqui está minha construção com base nesta resposta e nesta 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 o usa da seguinte maneira:

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, seria este:

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);

1

De todas as respostas que vi aqui, aqui e aqui , gostei mais da maneira de iterar do @sigidagi . Infelizmente, sua resposta é muito prolixa, o que, em minha opinião, obscurece a clareza inerente.

Esta é a minha versão de sua solução que é mais conciso e trabalha com std::tuple, std::paire 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_sequencepodem ser implementados para C ++ 11 .


0

A tupla de boost fornece funções auxiliares get_head()e, get_tail()portanto, suas funções auxiliares podem ter a seguinte aparência:

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()); }

conforme descrito aqui http://www.boost.org/doc/libs/1_34_0/libs/tuple/doc/tuple_advanced_interface.html

com std::tupleele deve ser semelhante.

Na verdade, infelizmente std::tuplenão parece fornecer essa interface, então os métodos sugeridos antes devem funcionar, ou você precisaria mudar para os boost::tuplequais tem outros benefícios (como operadores io já fornecidos). Embora haja uma desvantagem boost::tuplecom o gcc - ele ainda não aceita modelos variados, mas isso pode já estar corrigido, pois não tenho a última versão do boost instalado em minha máquina.


0

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

0

Outra opção seria implementar iteradores para tuplas. Isso tem a vantagem de poder usar uma variedade de algoritmos fornecidos pela biblioteca padrão e loops for baseados em intervalo. Uma abordagem elegante para isso é explicada aqui https://foonathan.net/2017/03/tuple-iterator/ . A ideia básica é transformar tuplas em um intervalo com métodos begin()e end()para fornecer iteradores. O próprio iterador retorna um std::variant<...>que pode então ser visitado usando std::visit.

Aqui estão alguns exemplos:

auto t = std::tuple{ 1, 2.f, 3.0 };
auto r = to_range(t);

for(auto v : r)
{
    std::visit(unwrap([](auto& x)
        {
            x = 1;
        }), v);
}

std::for_each(begin(r), end(r), [](auto v)
    {
        std::visit(unwrap([](auto& x)
            {
                x = 0;
            }), v);
    });

std::accumulate(begin(r), end(r), 0.0, [](auto acc, auto v)
    {
        return acc + std::visit(unwrap([](auto& x)
        {
            return static_cast<double>(x);
        }), v);
    });

std::for_each(begin(r), end(r), [](auto v)
{
    std::visit(unwrap([](const auto& x)
        {
            std::cout << x << std::endl;
        }), v);
});

std::for_each(begin(r), end(r), [](auto v)
{
    std::visit(overload(
        [](int x) { std::cout << "int" << std::endl; },
        [](float x) { std::cout << "float" << std::endl; },
        [](double x) { std::cout << "double" << std::endl; }), v);
});

Minha implementação (que é fortemente baseada nas explicações do link acima):

#ifndef TUPLE_RANGE_H
#define TUPLE_RANGE_H

#include <utility>
#include <functional>
#include <variant>
#include <type_traits>

template<typename Accessor>
class tuple_iterator
{
public:
    tuple_iterator(Accessor acc, const int idx)
        : acc_(acc), index_(idx)
    {

    }

    tuple_iterator operator++()
    {
        ++index_;
        return *this;
    }

    template<typename T>
    bool operator ==(tuple_iterator<T> other)
    {
        return index_ == other.index();
    }

    template<typename T>
    bool operator !=(tuple_iterator<T> other)
    {
        return index_ != other.index();
    }

    auto operator*() { return std::invoke(acc_, index_); }

    [[nodiscard]] int index() const { return index_; }

private:
    const Accessor acc_;
    int index_;
};

template<bool IsConst, typename...Ts>
struct tuple_access
{
    using tuple_type = std::tuple<Ts...>;
    using tuple_ref = std::conditional_t<IsConst, const tuple_type&, tuple_type&>;

    template<typename T>
    using element_ref = std::conditional_t<IsConst,
        std::reference_wrapper<const T>,
        std::reference_wrapper<T>>;

    using variant_type = std::variant<element_ref<Ts>...>;
    using function_type = variant_type(*)(tuple_ref);
    using table_type = std::array<function_type, sizeof...(Ts)>;

private:
    template<size_t Index>
    static constexpr function_type create_accessor()
    {
        return { [](tuple_ref t) -> variant_type
        {
            if constexpr (IsConst)
                return std::cref(std::get<Index>(t));
            else
                return std::ref(std::get<Index>(t));
        } };
    }

    template<size_t...Is>
    static constexpr table_type create_table(std::index_sequence<Is...>)
    {
        return { create_accessor<Is>()... };
    }

public:
    static constexpr auto table = create_table(std::make_index_sequence<sizeof...(Ts)>{}); 
};

template<bool IsConst, typename...Ts>
class tuple_range
{
public:
    using tuple_access_type = tuple_access<IsConst, Ts...>;
    using tuple_ref = typename tuple_access_type::tuple_ref;

    static constexpr auto tuple_size = sizeof...(Ts);

    explicit tuple_range(tuple_ref tuple)
        : tuple_(tuple)
    {
    }

    [[nodiscard]] auto begin() const 
    { 
        return tuple_iterator{ create_accessor(), 0 };
    }

    [[nodiscard]] auto end() const 
    { 
        return tuple_iterator{ create_accessor(), tuple_size };
    }

private:
    tuple_ref tuple_;

    auto create_accessor() const
    { 
        return [this](int idx)
        {
            return std::invoke(tuple_access_type::table[idx], tuple_);
        };
    }
};

template<bool IsConst, typename...Ts>
auto begin(const tuple_range<IsConst, Ts...>& r)
{
    return r.begin();
}

template<bool IsConst, typename...Ts>
auto end(const tuple_range<IsConst, Ts...>& r)
{
    return r.end();
}

template <class ... Fs>
struct overload : Fs... {
    explicit overload(Fs&&... fs) : Fs{ fs }... {}
    using Fs::operator()...;

    template<class T>
    auto operator()(std::reference_wrapper<T> ref)
    {
        return (*this)(ref.get());
    }

    template<class T>
    auto operator()(std::reference_wrapper<const T> ref)
    {
        return (*this)(ref.get());
    }
};

template <class F>
struct unwrap : overload<F>
{
    explicit unwrap(F&& f) : overload<F>{ std::forward<F>(f) } {}
    using overload<F>::operator();
};

template<typename...Ts>
auto to_range(std::tuple<Ts...>& t)
{
    return tuple_range<false, Ts...>{t};
}

template<typename...Ts>
auto to_range(const std::tuple<Ts...>& t)
{
    return tuple_range<true, Ts...>{t};
}


#endif

O acesso somente leitura também é suportado passando um const std::tuple<>&para to_range().


0

Expandindo a resposta @Stypox, podemos tornar sua solução mais genérica (C ++ 17 em diante). Adicionando um argumento de função chamável:

template<size_t I = 0, typename... Tp, typename F>
void for_each_apply(std::tuple<Tp...>& t, F &&f) {
    f(std::get<I>(t));
    if constexpr(I+1 != sizeof...(Tp)) {
        for_each_apply<I+1>(t, std::forward<F>(f));
    }
}

Então, precisamos de uma estratégia para visitar cada tipo.

Vamos começar com alguns auxiliares (os dois primeiros retirados de cppreference):

template<class... Ts> struct overloaded : Ts... { using Ts::operator()...; };
template<class... Ts> overloaded(Ts...) -> overloaded<Ts...>;
template<class ... Ts> struct variant_ref { using type = std::variant<std::reference_wrapper<Ts>...>; };

variant_ref é usado para permitir que o estado das tuplas seja modificado.

Uso:

std::tuple<Foo, Bar, Foo> tuples;

for_each_apply(tuples,
               [](variant_ref<Foo, Bar>::type &&v) {
                   std::visit(overloaded {
                       [](Foo &arg) { arg.foo(); },
                       [](Bar const &arg) { arg.bar(); },
                   }, v);
               });

Resultado:

Foo0
Bar
Foo0
Foo1
Bar
Foo1

Para completar, aqui estão meus Bar& Foo:

struct Foo {
    void foo() {std::cout << "Foo" << i++ << std::endl;}
    int i = 0;
};
struct Bar {
    void bar() const {std::cout << "Bar" << std::endl;}
};
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.