Template tuple – een functie aanroepen op elk element

Mijn vraag staat in de code:

template<typename... Ts>
struct TupleOfVectors {
  std::tuple<std::vector<Ts>...> tuple;
  void do_something_to_each_vec() {
    //Question: I want to do this:
    //  "for each (N)": do_something_to_vec<N>()
    //How?
  }
  template<size_t N>
  void do_something_to_vec() {
    auto &vec = std::get<N>(tuple);
    //do something to vec
  }
};

Antwoord 1, autoriteit 100%

Je kunt dat vrij gemakkelijk doen met sommige indicesmachines. Gegeven een metafunctie gen_seqvoor het genereren van integer-reeksen tijdens compileren (ingekapseld door de klassesjabloon seq):

namespace detail
{
    template<int... Is>
    struct seq { };
    template<int N, int... Is>
    struct gen_seq : gen_seq<N - 1, N - 1, Is...> { };
    template<int... Is>
    struct gen_seq<0, Is...> : seq<Is...> { };
}

En de volgende functiesjablonen:

#include <tuple>
namespace detail
{
    template<typename T, typename F, int... Is>
    void for_each(T&& t, F f, seq<Is...>)
    {
        auto l = { (f(std::get<Is>(t)), 0)... };
    }
}
template<typename... Ts, typename F>
void for_each_in_tuple(std::tuple<Ts...> const& t, F f)
{
    detail::for_each(t, f, detail::gen_seq<sizeof...(Ts)>());
}

Je kunt de functie for_each_in_tuplehierboven op deze manier gebruiken:

#include <string>
#include <iostream>
struct my_functor
{
    template<typename T>
    void operator () (T&& t)
    {
        std::cout << t << std::endl;
    }
};
int main()
{
    std::tuple<int, double, std::string> t(42, 3.14, "Hello World!");
    for_each_in_tuple(t, my_functor());
}

Hier is een live voorbeeld.

In uw concrete situatie zou u het zo kunnen gebruiken:

template<typename... Ts>
struct TupleOfVectors
{
    std::tuple<std::vector<Ts>...> t;
    void do_something_to_each_vec()
    {
        for_each_in_tuple(t, tuple_vector_functor());
    }
    struct tuple_vector_functor
    {
        template<typename T>
        void operator () (T const &v)
        {
            // Do something on the argument vector...
        }
    };
};

En nogmaals, hier is een live voorbeeld.

Bijwerken

Als u C++14of hoger gebruikt, kunt u de klassen seqen gen_seqhierboven vervangen door std::integer_sequencezoals zo:

namespace detail
{
    template<typename T, typename F, int... Is>
    void
    for_each(T&& t, F f, std::integer_sequence<int, Is...>)
    {
        auto l = { (f(std::get<Is>(t)), 0)... };
    }
} // namespace detail
template<typename... Ts, typename F>
void
for_each_in_tuple(std::tuple<Ts...> const& t, F f)
{
    detail::for_each(t, f, std::make_integer_sequence<int, sizeof...(Ts)>());
}

Als je C++17of hoger gebruikt, kun je dit doen (van deze opmerking hieronder ):

std::apply([](auto ...x){std::make_tuple(some_function(x)...);} , the_tuple);

Antwoord 2, autoriteit 93%

In C++17 kun je dit doen:

std::apply([](auto ...x){std::make_tuple(some_function(x)...);} , the_tuple);

aangezien some_functiongeschikte overbelastingen heeft voor alle typen in de tuple.

Dit werkt al in Clang++ 3.9, met behulp van std::experimental::apply.


Antwoord 3, autoriteit 76%

Naast het antwoordvan @M. Alaggan, als je een functie op tuple-elementen in volgorde van hun verschijningin de tuple moet aanroepen, kun je in C++17 ook een vouwuitdrukking zoals deze gebruiken :

std::apply([](auto& ...x){(..., some_function(x));}, the_tuple);

(live voorbeeld).

Omdat anders volgorde van evaluatie van functieargumenten niet gespecificeerd is.


Antwoord 4, autoriteit 17%

Hier is een benadering die in uw geval goed kan werken:

template<typename... Ts>
struct TupleOfVectors {
    std::tuple<std::vector<Ts>...> tuple;
    void do_something_to_each_vec()
    {
        // First template parameter is just a dummy.
        do_something_to_each_vec_helper<0,Ts...>();
    }
    template<size_t N>
    void do_something_to_vec()
    {
        auto &vec = std::get<N>(tuple);
        //do something to vec
    }
private:
    // Anchor for the recursion
    template <int>
    void do_something_to_each_vec_helper() { }
    // Execute the function for each template argument.
    template <int,typename Arg,typename...Args>
    void do_something_to_each_vec_helper()
    {
        do_something_to_each_vec_helper<0,Args...>();
        do_something_to_vec<sizeof...(Args)>();
    }
};

Het enige dat hier een beetje rommelig is, is de extra dummy intsjabloonparameter voor do_something_to_each_vec_helper. Het is noodzakelijk om de do_something_to_each_vec_helper nog steeds een sjabloon te maken als er geen argumenten meer zijn. Als je een andere sjabloonparameter had die je wilde gebruiken, zou je die daar kunnen gebruiken.


Antwoord 5, autoriteit 13%

Als u niet echt gehecht bent aan een oplossing in de vorm van generieke
“voor elke” functiesjabloon kunt u er een gebruiken zoals deze:

#ifndef TUPLE_OF_VECTORS_H
#define TUPLE_OF_VECTORS_H
#include <vector>
#include <tuple>
#include <iostream>
template<typename... Ts>
struct TupleOfVectors 
{
    std::tuple<std::vector<Ts>...> tuple;
    template<typename ...Args>
    TupleOfVectors(Args... args)
    : tuple(args...){}
    void do_something_to_each_vec() {
        do_something_to_vec(tuple);
    }
    template<size_t I = 0, class ...P>
    typename std::enable_if<I == sizeof...(P)>::type
    do_something_to_vec(std::tuple<P...> &) {}
    template<size_t I = 0, class ...P>
    typename std::enable_if<I < sizeof...(P)>::type
    do_something_to_vec(std::tuple<P...> & parts) {
        auto & part = std::get<I>(tuple);
        // Doing something...
        std::cout << "vector[" << I << "][0] = " << part[0] << std::endl;
        do_something_to_vec<I + 1>(parts);
    }
};
#endif // EOF

Een testprogramma, gebouwd met GCC 4.7.2 en clang 3.2:

#include "tuple_of_vectors.h"
using namespace std;
int main()
{
    TupleOfVectors<int,int,int,int> vecs(vector<int>(1,1),
        vector<int>(2,2),
        vector<int>(3,3),
        vector<int>(4,4));
    vecs.do_something_to_each_vec();
    return 0;
}

Dezelfde stijl van recursie kan worden gebruikt in een generieke “for_each”
functiesjabloon zonder hulpindices-apparaat:

#ifndef FOR_EACH_IN_TUPLE_H
#define FOR_EACH_IN_TUPLE_H
#include <type_traits>
#include <tuple>
#include <cstddef>
template<size_t I = 0, typename Func, typename ...Ts>
typename std::enable_if<I == sizeof...(Ts)>::type
for_each_in_tuple(std::tuple<Ts...> &, Func) {}
template<size_t I = 0, typename Func, typename ...Ts>
typename std::enable_if<I < sizeof...(Ts)>::type
for_each_in_tuple(std::tuple<Ts...> & tpl, Func func) 
{
    func(std::get<I>(tpl));
    for_each_in_tuple<I + 1>(tpl,func);
}
#endif //EOF

En een testprogramma daarvoor:

#include "for_each_in_tuple.h"
#include <iostream>
struct functor
{
    template<typename T>
    void operator () (T&& t)
    {
        std::cout << t << std::endl;
    }
};
int main()
{
    auto tpl = std::make_tuple(1,2.0,"Three");
    for_each_in_tuple(tpl,functor());
    return 0;
}

Antwoord 6, autoriteit 4%

Ik was aan het testen met tupels en metaprogrammering en vond de huidige thread.
Ik denk dat mijn werk iemand anders kan inspireren, hoewel ik de oplossing van @Andy leuk vind.

Hoe dan ook, maak gewoon plezier!

#include <tuple>
#include <type_traits>
#include <iostream>
#include <sstream>
#include <functional>
template<std::size_t I = 0, typename Tuple, typename Func>
typename std::enable_if< I != std::tuple_size<Tuple>::value, void >::type
for_each(const Tuple& tuple, Func&& func)
{
    func(std::get<I>(tuple));
    for_each<I + 1>(tuple, func);
}
template<std::size_t I = 0, typename Tuple, typename Func>
typename std::enable_if< I == std::tuple_size<Tuple>::value, void >::type
for_each(const Tuple& tuple, Func&& func)
{
    // do nothing
}
struct print
{
    template<typename T>
    void operator () (T&& t)
    {
        std::cout << t << std::endl;
    }
};
template<typename... Params>
void test(Params&& ... params)
{
    int sz = sizeof...(params);
    std::tuple<Params...> values(std::forward<Params>(params)...);
    for_each(values, print() );
}
class MyClass
{
public:
    MyClass(const std::string& text) 
        : m_text(text)
    {
    }
    friend std::ostream& operator <<(std::ostream& stream, const MyClass& myClass)
    {
        stream << myClass.m_text;
        return stream;
    }
private:
    std::string m_text;
};
int main()
{
    test(1, "hello", 3.f, 4, MyClass("I don't care") );
}

Antwoord 7

Boost mp11 heeft deze functionaliteit:

#include <iostream>
#include <string>
#include <boost/mp11.hpp>
using namespace std;
using boost::mp11::tuple_for_each;
std::tuple t{string("abc"), 47 };
int main(){
    tuple_for_each(t,[](const auto& x){
        cout << x + x << endl;
    });
}

Other episodes