2012-02-03 18 views
12

Mi piacerebbe creare il prodotto incrociato di un elenco di tipi utilizzando modelli variadic.Come creare il prodotto cartesiano di una lista di tipi?

Ecco quello che ho finora:

uscite
#include <iostream> 
#include <typeinfo> 
#include <cxxabi.h> 

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

template<typename T1, typename T2> struct type_pair {}; 

template<typename T, typename... Rest> 
    struct row 
{ 
    typedef type_list<type_pair<T,Rest>...> type; 
}; 

template<typename... T> 
    struct cross_product 
{ 
    typedef type_list<typename row<T,T...>::type...> type; 
}; 

int main() 
{ 
    int s; 
    typedef cross_product<int, float, short>::type result; 
    std::cout << abi::__cxa_demangle(typeid(result).name(), 0, 0, &s) << std::endl; 

    return 0; 
} 

questo programma:

$ g++ -std=c++0x cross_product.cpp ; ./a.out 
type_list<type_list<type_pair<int, int>, type_pair<int, float>, type_pair<int, short> >, type_list<type_pair<float, int>, type_pair<float, float>, type_pair<float, short> >, type_list<type_pair<short, int>, type_pair<short, float>, type_pair<short, short> > > 

Ma mi piacerebbe che in uscita:

type_list<type_pair<int,int>, type_pair<int,float>, type_pair<int,short>, type_pair<float,int>,...> 

Cioè, senza la nidificato type_list s.

Esiste un modo diretto per eseguire questa operazione senza l'helper row oppure la soluzione "scartare" in qualche modo l'nidificazione di type_list s?

+0

'__cxa_demangle' chiama malloc sotto, in modo che siano responsabili di [liberare la memoria] (http://gcc.gnu.org/onlinedocs/libstdc++/libstdc++-html-USERS-4.3/a01696.html). –

+4

Questo è compito! ';)' –

+4

Sappiamo tutti da dove viene quella domanda. E * tu * sai che ci è stato dato come compito di Andrei. :) – Xeo

risposta

7

In qualche modo il mio cervello è fritto: Penso che sto usando più il codice di quanto sia necessario, ma, almeno, ha i risultati desiderati (anche se non ha riparato la perdita di memoria):

#include <iostream> 
#include <typeinfo> 
#include <cxxabi.h> 

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

template<typename T1, typename T2> struct type_pair {}; 

template<typename T, typename... Rest> 
    struct row 
{ 
    typedef type_list<type_pair<T,Rest>...> type; 
}; 

template <typename... T> struct concat; 
template <typename... S, typename... T> 
struct concat<type_list<S...>, type_list<T...>> 
{ 
    typedef type_list<S..., T...> type; 
}; 

template <typename... T> 
struct expand 
{ 
    typedef type_list<T...> type; 
}; 
template <> struct expand<> { typedef type_list<> type; }; 
template <typename... T, typename... L> 
struct expand<type_list<T...>, L...> 
{ 
    typedef typename concat<typename expand<T...>::type, typename expand<L...>::type>::type type; 
}; 

template<typename... T> 
    struct cross_product 
{ 
    typedef typename expand<type_list<typename row<T,T...>::type...>>::type type; 

}; 

int main() 
{ 
    int s; 
    typedef cross_product<int, float, short>::type result; 
    std::cout << abi::__cxa_demangle(typeid(result).name(), 0, 0, &s) << std::endl; 

    return 0; 
} 
4

Forse qualcosa di simile:

template <typename ...Args> struct typelist { }; 

template <typename S, typename T> struct typelist_cat; 

template <typename ...Ss, typename ...Ts> 
struct typelist_cat<typelist<Ss...>, typelist<Ts...>> 
{ 
    typedef typelist<Ss..., Ts...> type; 
}; 


template <typename S, typename T> struct product; 

template <typename S, typename ...Ss, typename ...Ts> 
struct product<typelist<S, Ss...>, typelist<Ts...>> 
{ 
    // the cartesian product of {S} and {Ts...} 
    // is a list of pairs -- here: a typelist of 2-element typelists 
    typedef typelist<typelist<S, Ts>...> S_cross_Ts; 

    // the cartesian product of {Ss...} and {Ts...} (computed recursively) 
    typedef typename product<typelist<Ss...>, typelist<Ts...>>::type 
     Ss_cross_Ts; 

    // concatenate both products 
    typedef typename typelist_cat<S_cross_Ts, Ss_cross_Ts>::type type; 
}; 

// end the recursion 
template <typename ...Ts> 
struct product<typelist<>, typelist<Ts...>> 
{ 
    typedef typelist<> type; 
}; 

Ora si dovrebbe essere in grado di utilizzare product<typelist<A,B,C>, typelist<D,E,F>>::type.

+0

'typename T' non definito per i modelli 3a e 4a. – keveman

+0

@keveman: corretto, grazie. –

+0

Non si compila. Non usa coppie di tipi per abbinare i tipi per formare il prodotto cartesiano, quindi non so come funziona. – keveman

2

Ecco un'altra soluzione.

#include <iostream> 
#include <typeinfo> 
#include <cxxabi.h> 

template <typename ...Args> struct typelist { }; 
template <typename, typename> struct typepair { }; 

template <typename S, typename T> struct product; 
template <typename S, typename T> struct append; 

template<typename ...Ss, typename ...Ts> 
struct append<typelist<Ss...>, typelist<Ts...>> { 
    typedef typelist<Ss..., Ts...> type; 
}; 

template<> 
struct product<typelist<>, typelist<>> { 
    typedef typelist<> type; 
}; 

template<typename ...Ts> 
struct product<typelist<>, typelist<Ts...>> { 
    typedef typelist<> type; 
}; 

template<typename ...Ts> 
struct product<typelist<Ts...>, typelist<>> { 
    typedef typelist<> type; 
}; 

template<typename S, typename T, typename ...Ss, typename ...Ts> 
struct product<typelist<S, Ss...>, typelist<T, Ts...>> { 
    typedef typename 
      append<typelist<typepair<S, T>, 
          typepair<S, Ts>..., 
          typepair<Ss, T>...>, 
     typename product<typelist<Ss...>, typelist<Ts...>>::type>::type type; 
}; 

int main(void) 
{ 
    int s; 
    std::cout << abi::__cxa_demangle(
    typeid(product<typelist<int, float>, typelist<short, double>>::type).name(), 0, 0, &s)  << "\n"; 
    return 0; 
} 
8

Una bella versione pulita penso:

cross_product.cpp:

#include "type_printer.hpp" 

#include <iostream> 

template<typename ...Ts> struct type_list {}; 
template<typename T1, typename T2> struct pair {}; 

// Concatenation 
template <typename ... T> struct concat; 
template <typename ... Ts, typename ... Us> 
struct concat<type_list<Ts...>, type_list<Us...>> 
{ 
    typedef type_list<Ts..., Us...> type; 
}; 

// Cross Product 
template <typename T, typename U> struct cross_product; 

// Partially specialise the empty case for the first type_list. 
template <typename ...Us> 
struct cross_product<type_list<>, type_list<Us...>> { 
    typedef type_list<> type; 
}; 

// The general case for two type_lists. Process: 
// 1. Expand out the head of the first type_list with the full second type_list. 
// 2. Recurse the tail of the first type_list. 
// 3. Concatenate the two type_lists. 
template <typename T, typename ...Ts, typename ...Us> 
struct cross_product<type_list<T, Ts...>, type_list<Us...>> { 
    typedef typename concat< 
     type_list<pair<T, Us>...>, 
     typename cross_product<type_list<Ts...>, type_list<Us...>>::type 
    >::type type; 
}; 

struct A {}; 
struct B {}; 
struct C {}; 
struct D {}; 
struct E {}; 
struct F {}; 

template <typename T, typename U> 
void test() 
{ 
    std::cout << print_type<T>() << " \u2a2f " << print_type<U>() << " = " 
     << print_type<typename cross_product<T, U>::type>() << std::endl; 
} 

int main() 
{ 
    std::cout << "Cartesian product of type lists\n"; 
    test<type_list<>, type_list<>>(); 
    test<type_list<>, type_list<A>>(); 
    test<type_list<>, type_list<A, B>>(); 
    test<type_list<A, B>, type_list<>>(); 
    test<type_list<A>, type_list<B>>(); 
    test<type_list<A>, type_list<B, C, D>>(); 
    test<type_list<A, B>, type_list<B, C, D>>(); 
    test<type_list<A, B, C>, type_list<D>>(); 
    test<type_list<A, B, C>, type_list<D, E, F>>(); 
    return 0; 
} 

type_printer.hpp:

#ifndef TYPE_PRINTER_HPP 
#define TYPE_PRINTER_HPP 

#include "detail/type_printer_detail.hpp" 

template <typename T> 
std::string print_type() 
{ 
    return detail::type_printer<T>()(); 
} 

#endif 

dettaglio/type_printer_d etail.hpp:

#ifndef DETAIL__TYPE_PRINTER_DETAIL_HPP 
#define DETAIL__TYPE_PRINTER_DETAIL_HPP 

#include <typeinfo> 
#include <cxxabi.h> 
#include <string> 

template <typename ...Ts> struct type_list; 
template <typename T1, typename T2> struct pair; 

namespace detail { 

// print scalar types 
template <typename T> 
struct type_printer { 
    std::string operator()() const { 
     int s; 
     return abi::__cxa_demangle(typeid(T).name(), 0, 0, &s); 
    } 
}; 

// print pair<T, U> types 
template <typename T, typename U> 
struct type_printer<pair<T, U>> { 
    std::string operator()() const { 
     return "(" + type_printer<T>()() + "," + type_printer<U>()() + ")"; 
    } 
}; 

// print type_list<T> 
template <> 
struct type_printer<type_list<>> { 
    std::string operator()() const { 
     return "\u2205"; 
    } 
}; 

template <typename T> 
struct type_printer<type_list<T>> { 
    std::string operator()() const { 
     return "{" + type_printer<T>()() + "}"; 
    } 
    std::string operator()(const std::string& sep) const { 
     return sep + type_printer<T>()(); 
    } 
}; 

template <typename T, typename ...Ts> 
struct type_printer<type_list<T, Ts...>> { 
    std::string operator()() const { 
     return "{" + type_printer<T>()() + type_printer<type_list<Ts...>>()(std::string(", ")) + "}"; 
    } 
    std::string operator()(const std::string& sep) const { 
     return sep + type_printer<T>()() + type_printer<type_list<Ts...>>()(sep); 
    } 
}; 
} 

#endif 

Run:

g++ -std=c++0x cross_product.cpp && ./a.out 

uscita:

Cartesian product of type lists 
∅ ⨯ ∅ = ∅ 
∅ ⨯ {A} = ∅ 
∅ ⨯ {A, B} = ∅ 
{A, B} ⨯ ∅ = ∅ 
{A} ⨯ {B} = {(A,B)} 
{A} ⨯ {B, C, D} = {(A,B), (A,C), (A,D)} 
{A, B} ⨯ {B, C, D} = {(A,B), (A,C), (A,D), (B,B), (B,C), (B,D)} 
{A, B, C} ⨯ {D} = {(A,D), (B,D), (C,D)} 
{A, B, C} ⨯ {D, E, F} = {(A,D), (A,E), (A,F), (B,D), (B,E), (B,F), (C,D), (C,E), (C,F)} 

(ho notato su Windows utilizzando Chrome che il carattere trasversale del prodotto unicode non è venuta bene. Spiacente, non so come risolvere il problema.)

+0

Molto liscio e minimale. Complimenti! –

0

Nota: Questo è NON quello che l'OP chiedeva ... ma può essere rilevante per gli altri (come me) che incappano in questa domanda. Ecco come può essere fatto usando un Loki::TypeList (vale a dire il precedente C++ - 11), forse di interesse storico o per motivi di compatibilità.

Inoltre, forse è presuntuoso da parte mia inquinare lo spazio dei nomi di loki. YMMV.

crossproduct.h

#include "loki/NullType.h" 
#include "loki/Typelist.h" 

namespace Loki { 
namespace TL { 

/// a pair of two types 
template <typename A_t, typename B_t> 
struct TypePair 
{ 
    typedef A_t A; 
    typedef B_t B; 
}; 


/// a template which takes one type and pairs it with all other types 
/// in another typelist 
template <class T, class TList > struct DistributePair; 

/// specialization of Distribute for the nulltype 
template < class TList > 
struct DistributePair< NullType, TList > 
{ 
    typedef NullType type; 
}; 


/// specialization of Distribute where the second parameter is nulltype 
template <class T > 
struct DistributePair< T, NullType > 
{ 
    typedef NullType type; 
}; 

/// specialization of Distribute where the first parameter is a 
/// typelist 
template <class T, class Head, class Tail > 
struct DistributePair< T, Typelist<Head,Tail> > 
{ 
    typedef Typelist< 
       TypePair<T,Head>, 
       typename DistributePair<T,Tail>::type 
        > type; 
}; 

/// performs cartesion product of two typelists 
template <class TListA, class TListB> struct CrossProduct; 

/// specialization of CrossProduct for NullType 
template <class TListB> 
struct CrossProduct< NullType, TListB > 
{ 
    typedef NullType type; 
}; 

/// specialization of CrossProduct for recursion 
template <class Head, class Tail, class TListB> 
struct CrossProduct< Typelist<Head,Tail>, TListB > 
{ 
    typedef typename Append< 
      typename DistributePair< Head,TListB >::type, 
      typename CrossProduct< Tail, TListB >::type 
       >::Result type; 
}; 

} // namespace TL 
} // namespace Loki 

test.cpp

#include <crossproduct.h> 
#include <loki/HierarchyGenerators.h> 
#include <iostream> 

struct A{}; 
struct B{}; 
struct C{}; 

struct D{}; 
struct E{}; 
struct F{}; 

typedef LOKI_TYPELIST_3(A,B,C) TypeListA_t; 
typedef LOKI_TYPELIST_3(D,E,F) TypeListB_t; 

typedef typename Loki::TL::CrossProduct< TypeListA_t, TypeListB_t >::type Cross_t; 

template <typename T> const char* toString(); 

template <> const char* toString<A>(){ return "A"; }; 
template <> const char* toString<B>(){ return "B"; }; 
template <> const char* toString<C>(){ return "C"; }; 
template <> const char* toString<D>(){ return "D"; }; 
template <> const char* toString<E>(){ return "E"; }; 
template <> const char* toString<F>(){ return "F"; }; 

template <typename T> struct Printer 
{ 
    Printer() 
    { 
     std::cout << toString<T>() << ", "; 
    } 
}; 

template <typename T1, typename T2> 
struct Printer< Loki::TL::TypePair<T1,T2> > 
{ 
    Printer() 
    { 
     std::cout << "(" << toString<T1>() << "," << toString<T2>() << "), "; 
    } 
}; 


typedef Loki::GenScatterHierarchy< TypeListA_t, Printer > PrinterA_t; 
typedef Loki::GenScatterHierarchy< TypeListB_t, Printer > PrinterB_t; 
typedef Loki::GenScatterHierarchy< Cross_t, Printer >  PrinterCross_t; 

int main(int argc, char** argv) 
{ 
    std::cout << "\nType list A: \n "; 
    PrinterA_t a; 
    std::cout << "\nType list B: \n "; 
    PrinterB_t b; 
    std::cout << "\nType list Cross: \n "; 
    PrinterCross_t cross; 

    return 0; 
} 

uscita

Type list A: 
    A, B, C, 
Type list B: 
    D, E, F, 
Type list Cross: 
    (A,D), (A,E), (A,F), (B,D), (B,E), (B,F), (C,D), (C,E), (C,F), 
0

davvero apprezzato questo incarico "compiti a casa" :)

Entrambe le soluzioni di seguito creano una classe completa di type_list typedefs, insieme alle funzioni membro che verificheranno se un determinato elenco di tipi esiste nella classe come tipo_elenco.

La prima soluzione crea tutte le combinazioni possibili di tipi da 1 a N tipi per tipo_elenco (il parametro width definisce N). Ho fatto solo test superficiali ma sono abbastanza sicuro che stia creando tutte le combinazioni possibili. La seconda soluzione crea solo coppie di tipi.

prima soluzione

template<typename... Ts> struct type_list { typedef type_list<Ts...> type; }; 

template<size_t, typename...> struct xprod_tlist_ {}; 

template<typename... Ts, typename... Us> 
struct xprod_tlist_<1, type_list<Ts...>, Us...> {}; 

template<size_t width, typename... Ts, typename... Us> 
struct xprod_tlist_<width, type_list<Ts...>, Us...> 
: type_list<Ts..., Us>... 
, xprod_tlist_<width - 1, type_list<Ts..., Us>, Us...>... {}; 

template<size_t width, typename... Ts> struct xprod_tlist 
: type_list<Ts>..., xprod_tlist_<width, type_list<Ts>, Ts...>... { 
    template<typename... Us> struct exists 
    : std::is_base_of<type_list<Us...>, xprod_tlist<width, Ts...>> {}; 

    template<typename... Us> struct assert_exists { 
     static_assert(exists<Us...>::value, "Type not present in list"); 
    }; 
}; 

Usage:

typedef xprod_tlist<5, int, char, string, float, double, long> X; 

//these pass 
X::assert_exists<int, int, int, int, int> assert_test1; 
X::assert_exists<double, float, char, int, string> assert_test2; 

//these fail 
X::assert_exists<char, char, char, char, char, char> assert_test3; 
X::assert_exists<int, bool> assert_test4; 

//true 
auto test1 = X::exists<int, int, int, int, int>::value; 
auto test2 = X::exists<double, float, char, int, string>::value; 

//false 
auto test3 = X::exists<char, char, char, char, char, char>::value; 
auto test4 = X::exists<int, bool>::value; 

seconda soluzione

template<class T, class U> struct type_pair { typedef type_pair<T, U> type; }; 
template<class... Ts> struct type_list {}; 
template<class...> struct xprod_tlist_ {}; 

template<class T, class... Ts, class... Us> 
struct xprod_tlist_<type_list<T, Ts...>, type_list<Us...>> 
: type_pair<T, Us>..., xprod_tlist_<type_list<Ts...>, type_list<Us...>> {}; 

template<class... Ts> 
struct xprod_tlist : xprod_tlist_<type_list<Ts...>, type_list<Ts...>> { 
    template<class T, class U> struct exists 
    : std::is_base_of<type_pair<T, U>, xprod_tlist<Ts...>> {}; 

    template<class T, class U> struct assert_exists { 
     static_assert(exists<T, U>::value, "Type not present in list"); 
    }; 
}; 

Uso:

typedef xprod_tlist<int, float, string> X; 

//these pass 
X::assert_exists<int, int> assert_test1; 
X::assert_exists<int, float> assert_test2; 
X::assert_exists<int, string> assert_test3; 
X::assert_exists<float, int> assert_test4; 
X::assert_exists<float, float> assert_test5; 
X::assert_exists<float, string> assert_test6; 
X::assert_exists<string, int> assert_test7; 
X::assert_exists<string, float> assert_test8; 
X::assert_exists<string, string> assert_test9; 

//this fails 
X::assert_exists<int, char> assert_test10; 

//true 
auto test1 = X::exists<int, int>::value; 
auto test2 = X::exists<int, float>::value; 
auto test3 = X::exists<int, string>::value; 
auto test4 = X::exists<float, int>::value; 
auto test5 = X::exists<float, float>::value; 
auto test6 = X::exists<float, string>::value; 
auto test7 = X::exists<string, int>::value; 
auto test8 = X::exists<string, float>::value; 
auto test9 = X::exists<string, string>::value; 

//false 
auto test10 = X::exists<int, char>::value; 
1

Finora tutte le soluzioni hanno inconvenienti, dipendenze non necessarie, inutili aiutanti e tutti sono limitati al potere cartesiano di due. La seguente soluzione non ha tali inconvenienti e supporta:

  1. Qualunque potenza cartesiano compreso 0.
  2. Tornando l'insieme vuoto se uno dei fattori è un insieme vuoto.
  3. Il codice è autonomo e non dipende da alcun file di inclusione.
  4. Gli ingressi della funzione possono essere di qualsiasi tipo di modello.
  5. Il tipo dell'elenco di output può essere specificato tramite il primo parametro .

In realtà era più difficile da implementare (ma buono come compito), quindi ho pensato. In realtà sto pensando di creare un piccolo generatore che mi consente una sintassi estesa dei template che rende queste cose davvero facili.

Semplificato il codice funziona come segue: product converte un elenco di input tuple<A...>,tuple<B...>,tuple<C...> in tuple<tuple<A>...>, tuple<B...>, tuple<C...>. Questo secondo elenco viene quindi passato a product_helper che esegue il calcolo del prodotto cartesiano ricorsivo.

template <typename... T> struct cat2; 

template <template<typename...> class R, typename... As, typename... Bs> 
struct cat2 <R<As...>, R<Bs...> > { 
     using type = R <As..., Bs...>; 
}; 

template <typename... Ts> struct product_helper; 

template <template<typename...> class R, typename... Ts> 
struct product_helper < R<Ts...> > { // stop condition 
     using type = R< Ts...>; 
}; 

template <template<typename...> class R, typename... Ts> 
struct product_helper < R<R<> >, Ts... > { // catches first empty tuple 
     using type = R<>; 
}; 

template <template<typename...> class R, typename... Ts, typename... Rests> 
struct product_helper < R<Ts...>, R<>, Rests... > { // catches any empty tuple except first 
     using type = R<>; 
}; 

template <template<typename...> class R, typename... X, typename H, typename... Rests> 
struct product_helper < R<X...>, R<H>, Rests... > { 
     using type1 = R <typename cat2<X,R<H> >::type...>; 
     using type = typename product_helper<type1, Rests...>::type; 
}; 

template <template<typename...> class R, typename... X, template<typename...> class Head, typename T, typename... Ts, typename... Rests> 
struct product_helper < R<X...>, Head<T, Ts...>, Rests... > { 
     using type1 = R <typename cat2<X,R<T> >::type...>; 
     using type2 = typename product_helper<R<X...> , R<Ts...> >::type; 
     using type3 = typename cat2<type1,type2>::type; 
     using type = typename product_helper<type3, Rests...>::type; 
}; 

template <template<typename...> class R, typename... Ts> struct product; 

template <template<typename...> class R> 
struct product <R> { // no input, R specifies the return type 
    using type = R<>; 
}; 

template <template<typename...> class R, template<typename...> class Head, typename... Ts, typename... Tail> 
struct product <R, Head<Ts...>, Tail... > { // R is the return type, Head<A...> is the first input list 
    using type = typename product_helper< R<R<Ts>...>, Tail... >::type; 
}; 

Ecco uno compilable example di come il codice può essere utilizzato.

+1

Mi sembra che si possa scrivere 'cat2' come' template struct cat2; template