Come posso creare un modo cartesiano di elenchi di tipi in C ++?


26

Autoesplicativo.

Fondamentalmente, dire che ho elenchi di tipi in questo modo:

using type_list_1 = type_list<int, somestructA>;
using type_list_2 = type_list<somestructB>;
using type_list_3 = type_list<double, short>;

Possono essere un numero variabile di elenchi di tipi.

Come posso ottenere una lista dei tipi di prodotti cartesiani?

result = type_list<
type_list<int, somestructB, double>,
type_list<int, somestructB, short>,
type_list<somestructA, somestructB, double>,
type_list<somestructA, somestructB, short>
>;

Mi sono dilettato su come creare un prodotto cartesiano a due vie come indicato qui: Come creare il prodotto cartesiano di un elenco di tipi? , ma in nessun modo sembra essere così banale.

Per ora ci sto provando ...

template <typename...> struct type_list{};

// To concatenate
template <typename... Ts, typename... Us>
constexpr auto operator|(type_list<Ts...>, type_list<Us...>) {
   return type_list{Ts{}..., Us{}...};
}

template <typename T, typename... Ts, typename... Us>
constexpr auto cross_product_two(type_list<T, Ts...>, type_list<Us...>) {
    return (type_list<type_list<T,Us>...>{} | ... | type_list<type_list<Ts, Us>...>{});
}

template <typename T, typename U, typename... Ts>
constexpr auto cross_product_impl() {
    if constexpr(sizeof...(Ts) >0) {
        return cross_product_impl<decltype(cross_product_two(T{}, U{})), Ts...>();
    } else {
        return cross_product_two(T{}, U{});
    }
}

Dirò solo che considerando quanto sia difficile farlo nel modo giusto, basta usare boost come nella risposta di Barry. Sfortunatamente devo essere bloccato con un approccio orientato alla mano perché usare boost o no è una decisione che viene da qualche altra parte :(


8
Oof, sei un ghiottone per punizione 😏
Lightness Races in Orbit

Faccio schifo, ma puoi modificare il prodotto cartesiano a 2 vie in modo che: 1) la prima lista dei tipi sia in realtà una lista dei tipi di carte di 1 tipo; 2) invece di concatenare due tipi dalle liste dei tipi, la metafunzione aggiungerebbe i tipi dalla seconda lista alle liste "secondarie" della prima lista dei tipi (in modo cartesiano)? Se è possibile, il problema può essere facilmente risolto con l'algoritmo ricorsivo.
smitsyn,

1
La vera difficoltà in un'implementazione ricorsiva è che cartesian_productè un elenco di elenchi di tipi e ad ogni fase di ricorsione si desidera aggiungere elementi a ciascun elenco di tipi interno. Arrivare a quel secondo livello di imballaggio richiede un po 'di detrazione ...
Max Langhof,

1
Immagino che potresti anche implementarlo "linearmente" osservandolo come uno "spazio di tipo" N-dimensionale in cui vuoi attraversare ogni "punto di griglia di tipo". Calcoli il numero di punti della griglia, quindi lo attraversi come faresti attraverso un array ND appiattito e calcoli i tipi in ciascun punto della griglia. Qualcosa da considerare ...
Max Langhof,

1
@MaxLanghof Qualcosa sulla falsariga di " Un prodotto cartesiano delle tuple in C ++ 17 "?
Deduplicatore,

Risposte:


14

Con Boost.Mp11 , questo è un breve liner (come sempre):

using result = mp_product<
    type_list,
    type_list_1, type_list_2, type_list_3>;

Demo .


1
Mucca santa ... Ma mi sento in dovere di sottolineare che (campionando ogni codice più volte su godbolt) la versione Mp11 impiega circa il doppio del tempo per essere compilata. Non sono sicuro di quanta parte dell'overhead stia analizzando l'header boost stesso e di quanto sia un'istanza dei template ...
Max Langhof,

1
@MaxLanghof Certo. 1,5x se includi solo algorithm.hppanziché Mp11. E anche allora stiamo parlando di 0,08 secondi contro 0,12 secondi. Devo tener conto di quanto tempo ho impiegato anche a scrivere questo.
Barry,

8
@ Barry: dal punto di vista dell'ingegneria del software, con te al 100%. C'è anche quanto sia facile da leggere rispetto a un approccio fatto a mano. Inoltre, per garantire la correttezza della soluzione della libreria non sono necessari test da eseguire. Complessivamente meno codice e maggiore affidabilità comporteranno minori costi di manutenzione per tutta la sua durata.
AndyG

Sono d'accordo che sia abbastanza semplice, ma sfortunatamente esistono squadre che aggrottano le sopracciglia.
themagicalyang,

ci sono squadre che aggrottano le sopracciglia su tutto. questo non è un motivo per non usarlo.
Tomaz Canabrava,

13

Ok capito. Non è carino ma funziona:

template<class ... T>
struct type_list{};

struct somestructA{};
struct somestructB{};

using type_list_1 = type_list<int, somestructA, char>;
using type_list_2 = type_list<somestructB>;
using type_list_3 = type_list<double, short, float>;

template<class TL1, class TL2>
struct add;

template<class ... T1s, class ... T2s>
struct add<type_list<T1s...>, type_list<T2s...>>
{
    using type = type_list<T1s..., T2s...>;
};

template<class ... TL>
struct concat;

template<class TL, class ... TLs>
struct concat<TL, TLs...>
{
    using type = typename add<TL, typename concat<TLs...>::type>::type;
};

template<class TL>
struct concat<TL>
{
    using type = TL;
};

static_assert(std::is_same_v<type_list<int, somestructA, char, double, short, float>, typename add<type_list_1, type_list_3>::type>);

template<class TL1, class TL2>
struct multiply_one;

// Prepends each element of T1 to the list T2.
template<class ... T1s, class ... T2s>
struct multiply_one<type_list<T1s...>, type_list<T2s...>>
{
    using type = typename concat<type_list<type_list<T1s, T2s...>...>>::type;
};

static_assert(std::is_same_v<
    type_list<
        type_list<int, double, short, float>,
        type_list<somestructA, double, short, float>,
        type_list<char, double, short, float>
        >,
    typename multiply_one<type_list_1, type_list_3>::type>);

// Prepends each element of TL to all type lists in TLL.
template<class TL, class TLL>
struct multiply_all;

template<class TL, class ... TLs>
struct multiply_all<TL, type_list<TLs...>>
{
    using type = typename concat<typename multiply_one<TL, TLs>::type...>::type;
};

static_assert(std::is_same_v<
    type_list<
        type_list<int, double, short, float>,
        type_list<somestructA, double, short, float>,
        type_list<char, double, short, float>
        >,
    typename multiply_all<type_list_1, type_list<type_list_3>>::type>);

static_assert(std::is_same_v<
    type_list<
        type_list<int, somestructB>,
        type_list<somestructA, somestructB>,
        type_list<char, somestructB>,
        type_list<int, double, short, float>,
        type_list<somestructA, double, short, float>,
        type_list<char, double, short, float>
        >,
    typename multiply_all<type_list_1, type_list<type_list_2, type_list_3>>::type>);

template<class TL, class ... TLs>
struct cartesian_product
{
    using type = typename multiply_all<TL, typename cartesian_product<TLs...>::type>::type;
};

template<class ... Ts>
struct cartesian_product<type_list<Ts...>>
{
    using type = type_list<type_list<Ts>...>;
};


using expected_result = type_list<
    type_list<int, somestructB, double>,
    type_list<somestructA, somestructB, double>,
    type_list<char, somestructB, double>,
    type_list<int, somestructB, short>,
    type_list<somestructA, somestructB, short>,
    type_list<char, somestructB, short>,
    type_list<int, somestructB, float>,
    type_list<somestructA, somestructB, float>,
    type_list<char, somestructB, float>
>;

static_assert(std::is_same_v<expected_result,
    cartesian_product<type_list_1, type_list_2, type_list_3>::type>);

https://godbolt.org/z/L5eamT

Ho lasciato i miei static_asserttest lì dentro per ... Beh, spero che aiutino.

Inoltre, sono sicuro che ci deve essere una soluzione migliore. Ma questo era l'ovvio percorso "So che alla fine porterà all'obiettivo". Alla fine ho dovuto ricorrere all'aggiunta di una concato più specie, sono sicuro che potrebbe essere usato molto prima per saltare la maggior parte della cruft.


4
Programmazione di template che posso seguire. È fantastico Ho imparato qualcosa oggi.
Jerry Jeremiah,

add accetta due type_lists. Come stai passando più elenchi di tipi da aggiungere in concat?
themagicalyang,

@themagicalyang Ben individuato, questo è un bug (che i test non hanno trovato perché tutte le liste coinvolte erano solo di lunghezza 2). La ...deve andare all'interno del ricorsiva concatchiamata, non al di fuori. Risposta (compresi i casi di test) corretta. Barry ha ragione riguardo alle aspettative di correttezza :)
Max Langhof,

La chiamata cartesiana del prodotto a multiply_all non è fondamentalmente una multiple_one?
themagicalyang,

@themagicalyang No. cartesian_productimplementa la ricorsione. multiply_allfa un multiply_oneper ogni elenco di tipi nel TLspacchetto. cartesian_product::typeè un elenco di elenchi di tipi. multiply_allaccetta un elenco di tipi e un elenco di elenchi di tipi. multiply_oneprende due liste di tipo a1, a2, a3e b1, b2, b3e crea a1, b1, b2, b3, a2, b1, b2, b3, a3, b1, b2, b3. Sono necessari questi due livelli di deduzione ( multiply_all, multiply_one) perché è necessario scendere di due livelli di "variabilità", vedere il mio primo commento sulla domanda.
Max Langhof,

9

Piega di nuovo le espressioni in soccorso

template<typename... Ts>
typelist<typelist<Ts>...> layered(typelist<Ts...>);

template<typename... Ts, typename... Us>
auto operator+(typelist<Ts...>, typelist<Us...>)
    -> typelist<Ts..., Us...>;

template<typename T, typename... Us>
auto operator*(typelist<T>, typelist<Us...>)
    -> typelist<decltype(T{} + Us{})...>;

template<typename... Ts, typename TL>
auto operator^(typelist<Ts...>, TL tl)
    -> decltype(((typelist<Ts>{} * tl) + ...));

template<typename... TLs>
using product_t = decltype((layered(TLs{}) ^ ...));

E hai finito. Ciò ha l'ulteriore vantaggio rispetto alla ricorsione di avere una profondità di istanza O (1).

struct A0;
struct A1;
struct B0;
struct B1;
struct C0;
struct C1;
struct C2;

using t1 = typelist<A0, A1>;
using t2 = typelist<B0, B1>;
using t3 = typelist<C0, C1, C2>; 

using p1 = product_t<t1, t2>;
using p2 = product_t<t1, t2, t3>;

using expect1 = typelist<typelist<A0, B0>,
                         typelist<A0, B1>,
                         typelist<A1, B0>,
                         typelist<A1, B1>>;

using expect2 = typelist<typelist<A0, B0, C0>,
                         typelist<A0, B0, C1>,
                         typelist<A0, B0, C2>,
                         typelist<A0, B1, C0>,
                         typelist<A0, B1, C1>,
                         typelist<A0, B1, C2>,
                         typelist<A1, B0, C0>,
                         typelist<A1, B0, C1>,
                         typelist<A1, B0, C2>,
                         typelist<A1, B1, C0>,
                         typelist<A1, B1, C1>,
                         typelist<A1, B1, C2>>;

static_assert(std::is_same_v<p1, expect1>);
static_assert(std::is_same_v<p2, expect2>);

Questo mi incuriosisce. C'è un modo per rappresentarlo come risultato TL1 * TL2 * TL3 = crossporduct?
themagicalyang,

@themagicalyang Cosa intendi per "risultato prodotto incrociato"?
Passer Entro il

in sostanza invece di using result = product_t<t1,t2,t3>... un modo per rappresentarlo come using result = decltype(t1{} * t2{} * t3{});. Bene, ora che ci pensa, dato che decltypeè inevitabile, semplicemente usare l'alias come hai dato è più intuitivo.
themagicalyang,

Interessante! L'uso del sovraccarico dell'operatore ti dà le espressioni fold invece delle ricorsioni che ho dovuto fare. Lo rende anche molto più conciso. Lo terrò a mente per la prossima volta!
Max Langhof,

@PasserBy Tutti quegli operatori e funzioni di supporto devono essere nello stesso spazio dei nomi? Sto riscontrando problemi con l'inserimento di tutto in uno spazio dei nomi e l'accesso a product_t utilizzando un alias dallo spazio dei nomi esterno.
themagicalyang,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.