2016-03-17 11 views
12

Supponiamo di avere tipi A, B con costruttori A(int a, double b, std::string c), B(double a, int b).Generazione di oggetti di tipi variabili da combinazioni di argomenti del costruttore

So come definire una funzione che istanzia o A o B tramite modelli variadic.

C'è un modo per progettare una funzione/macro/tipo che per un tipo T e una serie di vettori di possibilità per gli argomenti del costruttore di T mi fornisce tutti gli oggetti possibili?

Ad esempio, se uso questo costrutto magico per <A, {2, 5, 6}, {2.44, 3.14}, {"yes", "no"}> dovrebbe fornire gli oggetti:

A(2, 2.44, "yes") 
A(2, 2.44, "no") 
A(2, 3.14, "yes") 
... 
A(6, 3.14, "no") 

Lo stesso dovrebbe funzionare per B o qualsiasi altro tipo senza dover rielaborare il costrutto magico.

Questo è molto semplice in Python, ad esempio, ma non so se è possibile in C++.

+1

Quindi, hai ordinato set (tuple). Vuoi prendere il loro prodotto cartesiano e costruire un oggetto di un determinato tipo da ogni elemento di quel prodotto cartesiano? Cosa vuoi fare con questi oggetti a quel punto? Il tuo paragrafo iniziale menziona 'B', ma non ne parli mai più. Sospetto che possa essere modificato senza cambiare la tua domanda. All'interno di ogni tupla '{2,5,6}' possiamo assumere che tutti i valori siano dello stesso tipo o no? – Yakk

+1

_ "se uso questo costrutto magico per" _ Dovresti incarnare di più con il codice (pseudo) che vuoi usare. Come menzionato nella risposta, mostra almeno il tuo codice di esempio Python. –

+0

@Yakk: ho menzionato 'B' solo per sottolineare che la soluzione dovrebbe funzionare per più di un tipo (fisso). –

risposta

6

Questo utilizza std::experimental::array_view per l'efficienza. È possibile sostituirlo con std::vector a un costo di runtime, o una coppia di iteratori/puntatori con un certo costo di chiarezza.

template<class T> 
using array_view = std::experimental::array_view<T>; 

using indexes = array_view<std::size_t>; 

Questo itera il prodotto trasversale di ciascun elemento < rispettivo indice di indici. Quindi {3,3,2} come is itera su {0,0,0} quindi {0,0,1} fino a {2,2,1}.

template<class F> 
void for_each_cartesian_product(
    indexes is, 
    F&& f, 
    std::vector<std::size_t>& result 
) { 
    if (is.empty()) { 
    f(result); 
    return; 
    } 
    auto max_index = is.front(); 
    for (std::size_t i = 0; i < max_index; ++i) { 
    result.push_back(i); 
    for_each_cartesian_product({is.begin()+1, is.end()}, f, result); 
    result.pop_back(); 
    } 
} 
template<class F> 
void for_each_cartesian_product(
    indexes is, 
    F&& f 
) { 
    std::vector<size_t> buffer; 
    for_each_cartesian_product(is, f, buffer); 
} 

poi abbiamo appena popolano il nostro indici:

template<class...Ts> 
std::vector<std::size_t> get_indexes(std::vector<Ts> const&... vs) { 
    return {vs.size()...}; 
} 

Avanti, possiamo solo avere a prendere i nostri argomenti, metterli in un vettore, e quindi utilizzare gli indici per ottenere elementi di ogni vettore e passali a A per essere costruiti.

template<class T, std::size_t...Is, class...Args> 
std::vector<T> make_stuff(std::index_sequence<Is...>, std::vector<Args>const&... args) { 
    std::vector<T> retval; 
    for_each_cartesian_product(
    get_indexes(args...), 
    [&](auto&& index){ 
     retval.emplace_back(args[ index[Is] ]...); 
    } 
); 
    return retval; 
} 
template<class T, class...Args> 
std::vector<T> make_stuff(std::vector<Args>const&... args) { 
    return make_stuff<T>(std::index_sequence_for<Args...>{}, args...); 
} 

e bob è tuo zio.

Gli A s generati possono essere spostati.

È anche possibile eseguire questa operazione in fase di compilazione con array di tempo noto compilati.

index_sequence_for e index_sequence sono C++ 14, ma facili da implementare in C++ 11.Ci sono molti esempi sullo stack overflow.

Il codice sopra riportato non è stato compilato.

+0

'std :: vector (get_indexes (args ...)' dovrebbe probabilmente essere 'for_each_cartesian_product (get_indexes (args ...)' in 'make_stuff'. – Jarod42

+0

Grazie! Il pezzo mancante del puzzle era che puoi * fare * cose con gli argomenti '...' -ed, ho avuto l'impressione che tu possa semplicemente passarli così com'è. –

0

Quello che stai cercando è una "fabbrica", che può o non può chiamare uno o più costruttori, a seconda della tua implementazione. Certamente è possibile se A e B discendono dalla stessa classe genitore (o se uno di essi è discendente di un altro).

Sarebbe anche possibile scrivere una factory di gruppo che costruisca una raccolta di tutti gli oggetti necessari. Ovviamente, sarebbe una considerazione progettuale decidere come memorizzare i puntatori agli oggetti appena costruiti.

Mi piacerebbe essere più specifico, ma avrei bisogno di vedere almeno un esempio del codice Python a cui ti riferisci.

1

Ecco una versione C++ 14 del Vaughn Cato's solution (credito dove è dovuta) che può assumere entrambe omogenee contenitori (quelli che contengono tutti gli oggetti di un singolo tipo) e eterogeneistd::tuple contenitori (che può contenere oggetti di vario tipo) come input. Ciò ti consente di combinare tipi di parametri per chiamare più costruttori.

Questo sarebbe possibile implementare in C++ 11, ma richiederebbe un po 'più di codice per emulare l'acquisizione lambda generica di argomenti variadici. esempio

#include <algorithm> 
#include <tuple> 
#include <vector> 

template <typename Ts, size_t... Is, typename F> 
void for_each_impl(Ts const& t, std::index_sequence<Is...> is, F const& f) 
{ 
    using expand = int[]; 
    expand{ (f(std::get<Is>(t)), 0)... }; 
} 

template <typename... Ts, typename F> 
void for_each(std::tuple<Ts...> const& t, F const& f) 
{ 
    for_each_impl(t, std::make_index_sequence<sizeof...(Ts)>(), f); 
} 

template <typename T, typename F> 
void for_each(T const& t, F const& f) 
{ 
    std::for_each(std::begin(t), std::end(t), f); 
} 

template <std::size_t Index, std::size_t Count> 
struct cartesian_builder 
{ 
    template <typename T, typename Sets, typename... Args> 
    static void make(std::vector<T> &v, Sets const& sets, Args const&... args) 
    { 
     for_each(std::get<Index>(sets), [&](auto& arg) { 
      cartesian_builder<Index + 1, Count>::make(v, sets, args..., arg); 
     }); 
    } 
}; 

template <std::size_t Count> 
struct cartesian_builder<Count, Count> { 
    template <typename T, typename Sets, typename... Args> 
    static void make(std::vector<T>& v, Sets const&, Args const&... args) 
    { 
     v.emplace_back(args...); 
    } 
}; 

template <> 
struct cartesian_builder<0, 0> { 
    template <typename T, typename Sets, typename... Args> 
    static void make(std::vector<T>&, Sets const&, Args const&...) 
    { 
    } 
}; 

template <typename T, typename... Sets> 
static std::vector<T> make_cartesian_product(Sets const&... sets) 
{ 
    std::vector<T> v; 
    cartesian_builder<0, sizeof...(sets)>::make(v, std::tie(sets...)); 
    return v; 
} 

Usage:

#include <iostream> 
#include <set> 
#include <string> 
#include <vector> 

int main() 
{ 
    struct A 
    { 
     int a; 
     double b; 
     std::string c; 

     A(std::string a, double b, std::string c) : 
      a(0), b(b), c(a + " " + c) 
     { 
     } 

     A(int a, double b, std::string c) : 
      a(a), b(b), c(c) 
     { 
     } 
    }; 

    std::vector<A> objects = make_cartesian_product<A>(
     std::make_tuple(2, "maybe", 6), 
     std::set<double>{2.44, 3.14}, 
     std::vector<char const*>{"yes", "no"} 
    ); 

    for (auto& o : objects) 
    { 
     std::cout << "(" << o.a << ", " << o.b << ", " << o.c << ")\n"; 
    } 
} 
3

Ecco un approccio relativamente semplice:

#include <vector> 
#include <string> 
#include <iostream> 
#include <tuple> 

using std::vector; 
using std::cout; 
using std::tie; 


template <size_t index,size_t count> 
struct Maker { 
    template <typename T,typename Tuple,typename...Args> 
    static void make(vector<T> &v,const Tuple &tup,Args &...args) 
    { 
    for (auto &x : std::get<index>(tup)) { 
     Maker<index+1,count>::make(v,tup,args...,x); 
    } 
    } 
}; 

template<size_t index> 
struct Maker<index,index> { 
    template <typename T,typename Tuple,typename...Args> 
    static void make(vector<T> &v,const Tuple &,Args &...args) 
    { 
    v.push_back(T(args...)); 
    } 
}; 

template <typename T,typename...Ts> 
static vector<T> combinations(const Ts &...args) 
{ 
    vector<T> v; 
    Maker<0,sizeof...(args)>::make(v,tie(args...)); 
    return v; 
} 




int main() 
{ 
    struct A { 
    A(int,double,std::string) { } 
    }; 

    struct B { 
    B(double,int) { } 
    }; 

    vector<A> as = 
    combinations<A>(
     vector<int>{2,5,6}, 
     vector<double>{2.44,3.14}, 
     vector<const char *>{"yes","no"} 
    ); 
    vector<B> bs = 
    combinations<B>(
     vector<double>{2.44,3.14}, 
     vector<int>{2,5,6} 
    ); 
    cout << "as.size()=" << as.size() << "\n"; 
    cout << "bs.size()=" << bs.size() << "\n"; 
} 

uscita:

12 
6 
+0

Soluzione molto carina Ho cambiato la mia risposta a una versione del tuo approccio che funziona con 'std :: tuple'. –

2

La mia soluzione non è elegante come quella di Yacc (dopotutto sto cercando di imparare) bu t penso che sia relativamente semplice.

Commenti e segnalazioni di difetti sono i benvenuti.

#include <tuple> 
#include <vector> 
#include <complex> 
#include <iostream> 
#include <initializer_list> 


template <std::size_t ... N, typename X, typename ... Al> 
void cartHelp (std::vector<X> & v, 
       std::tuple<Al...> const & t) 
{ v.emplace_back(std::get<N>(t)...); } 


template <std::size_t ... N, typename X, typename ... Al1, typename L, 
    typename ... Al2> 
void cartHelp (std::vector<X> & v, 
       std::tuple<Al1...> const & t, 
       std::initializer_list<L> const & l, 
       std::initializer_list<Al2> const & ... al2) 
{ 
    for (auto const & elem : l) 
     cartHelp<N..., sizeof...(N)>(v, std::tuple_cat(t, std::tie(elem)), 
            al2...); 
} 



template <typename X, typename L, typename ... Al> 
std::vector<X> cartesian (std::initializer_list<L> const & l, 
          std::initializer_list<Al> const & ... al) 
{ 
    std::vector<X> v; 

    for (auto const & elem : l) 
     cartHelp<0>(v, std::tie(elem), al...); 

    return v; 
} 


int main() 
{ 
    auto v1 = cartesian<int>({1,2}); 

    std::cout << "--- v1.size --- " << v1.size() << "\n"; 
    std::cout << "v1"; 
    for (auto const & elem : v1) 
     std::cout << '[' << elem << ']'; 
    std::cout << '\n'; 

    auto v2 = cartesian<std::complex<double>> 
     ({1.2,2.3,3.4}, {11.11, 22.22, 33.33}); 

    std::cout << "--- v2.size --- " << v2.size() << "\n"; 
    std::cout << "v2"; 
    for (auto const & elem : v2) 
     std::cout << '[' << elem << ']'; 
    std::cout << '\n'; 

    auto v3 = cartesian<std::tuple<int, double, std::string>> 
     ({1, 2, 3, 4, 5}, {0.1, 0.2, 0.3, 0.4}, 
     {std::string("aaa"), std::string("bbb"), std::string("ccc")}); 

    std::cout << "--- v3.size --- " << v3.size() << "\n"; 
    std::cout << "v3"; 
    for (auto const & elem : v3) 
     std::cout << '[' << std::get<0>(elem) << ',' << std::get<1>(elem) 
     << ',' << std::get<2>(elem) << ']'; 
    std::cout << '\n'; 

    return 0; 
} 

uscita:

--- v1.size --- 2 
v1[1][2] 
--- v2.size --- 9 
v2[(1.2,11.11)][(1.2,22.22)][(1.2,33.33)][(2.3,11.11)][(2.3,22.22)][(2.3,33.33)][(3.4,11.11)][(3.4,22.22)][(3.4,33.33)] 
--- v3.size --- 60 
v3[1,0.1,aaa][1,0.1,bbb][1,0.1,ccc][1,0.2,aaa][1,0.2,bbb][1,0.2,ccc][1,0.3,aaa][1,0.3,bbb][1,0.3,ccc][1,0.4,aaa][1,0.4,bbb][1,0.4,ccc][2,0.1,aaa][2,0.1,bbb][2,0.1,ccc][2,0.2,aaa][2,0.2,bbb][2,0.2,ccc][2,0.3,aaa][2,0.3,bbb][2,0.3,ccc][2,0.4,aaa][2,0.4,bbb][2,0.4,ccc][3,0.1,aaa][3,0.1,bbb][3,0.1,ccc][3,0.2,aaa][3,0.2,bbb][3,0.2,ccc][3,0.3,aaa][3,0.3,bbb][3,0.3,ccc][3,0.4,aaa][3,0.4,bbb][3,0.4,ccc][4,0.1,aaa][4,0.1,bbb][4,0.1,ccc][4,0.2,aaa][4,0.2,bbb][4,0.2,ccc][4,0.3,aaa][4,0.3,bbb][4,0.3,ccc][4,0.4,aaa][4,0.4,bbb][4,0.4,ccc][5,0.1,aaa][5,0.1,bbb][5,0.1,ccc][5,0.2,aaa][5,0.2,bbb][5,0.2,ccc][5,0.3,aaa][5,0.3,bbb][5,0.3,ccc][5,0.4,aaa][5,0.4,bbb][5,0.4,ccc] 
0

auto lambda variadic (C++ 14) può essere utilizzata per memorizzare i riferimenti agli argomenti anziché indici intermedi.

Il codice diventa molto semplice, non è sicuro sulle prestazioni:

/** calls f for each element in c */ 
template <typename F, typename C> 
void for_each_product(F&& f, const C& c) { 
    for (auto& e: c) f(e); 
} 

/** calls f for each cartesian product from c,cs containers */ 
template <typename F, typename C, typename... Cs> 
void for_each_product(F&& f, const C& c, const Cs&... cs) { 
    for (auto& e: c) 
    for_each_product(
      [&f, &e](auto&&... args) { 
       f(e, args...); 
      }, cs...); 
} 

Questo è tutto :-)

Ecco come usarlo:

#include <vector> 
#include <string> 

struct A { 
    int i; 
    double d; 
    std::string s; 
    A(int _i, double _d, std::string _s): i(_i), d(_d), s(_s) {} 
}; 

int main() { 
    auto vi = std::vector<int>{2, 5, 6}; 
    auto vd = std::vector<double>{2.44, 3.14}; 
    auto vs = std::vector<std::string>{"yes", "no"}; 
    auto va = std::vector<A>(); 

    for_each_product([&va](auto&&... args){ 
     va.emplace_back(args...); 
    }, vi, vd, vs); 
} 

supporto aggiuntivo per elenchi di inizializzazione:

/** calls f for each element in c */ 
template <typename F, typename T> 
void for_each_product(F&& f, const std::initializer_list<T>& c){ 
    for (auto& e: c) f(e); 
} 

/** calls f for each cartesian product from c,cs containers */ 
template <typename F, typename T, typename... Ts> 
void for_each_product(F&& f, const std::initializer_list<T>& c, 
       const std::initializer_list<Ts>&... cs){ 
    for (auto& e: c) 
    for_each_product(
      [&f,&e](auto&&... args) { 
       f(e, args...); 
      }, cs...); 
} 

Poi si può usare in questo modo:

for_each_product([&va](auto&&... args){ va.emplace_back(args...); }, 
    {2, 5, 6}, 
    {2.44, 3.14}, 
    {"yes", "no"}); 

Tuttavia le liste di inizializzazione non si mescolano con i contenitori, non è sicuro come risolvere questo.

Problemi correlati