Comment puis-je créer un produit cartésien de listes de types en C ++?

26

Explicite.

Fondamentalement, disons que j'ai des listes de types comme ceci:

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

Ils peuvent être des nombres variés de listes de types.

Comment obtenir une liste de types de produits cartésiens?

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

J'ai essayé de créer un produit cartésien bidirectionnel comme indiqué ici: Comment créer le produit cartésien d'une liste de types? , mais rien ne semble être si insignifiant.

Pour l'instant j'essaye ...

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

Je dirai simplement que compte tenu de la difficulté de bien faire les choses, utilisez simplement boost comme dans la réponse de Barry. Malheureusement, je dois être bloqué avec une approche roulée à la main car utiliser boost ou non est une décision qui vient d'ailleurs :(

themagicalyang
la source
8
Oof, vous êtes un glouton pour la punition 😏
Courses de légèreté en orbite
Je crains un peu, mais pouvez-vous modifier le produit cartésien à 2 voies de manière à ce que: 1) la première liste de types soit en fait une liste de types de listes de 1 type; 2) au lieu de concaténer deux types de listes de types, la métafonction ajoutera-t-elle les types de la deuxième liste aux listes "enfants" de la première liste de types (de manière cartésienne)? Si cela est possible, le problème peut être facilement résolu avec un algorithme récursif.
smitsyn
1
La vraie difficulté dans une implémentation récursive est qu'il cartesian_products'agit d'une liste de listes de types, et à chaque étape de récursivité, vous voulez ajouter des éléments à chaque liste de types interne. Entrer dans ce deuxième niveau d'emballage prend une certaine déduction ...
Max Langhof
1
Je suppose que vous pouvez également l'implémenter "linéairement" en le considérant comme un "espace de type" à N dimensions où vous voulez traverser chaque "point de grille de type". Vous calculez le nombre de points de grille, puis vous le parcourez comme vous le feriez dans un tableau ND aplati et calculez les types à chaque point de grille. Quelque chose à considérer ...
Max Langhof
1
@MaxLanghof Quelque chose dans le sens de " Un produit cartésien de tuples en C ++ 17 "?
Déduplicateur

Réponses:

14

Avec Boost.Mp11 , il s'agit d'un court one-liner (comme toujours):

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

Démo .

Barry
la source
1
Holy cow ... Mais je me sens obligé de souligner que (en échantillonnant chaque code plusieurs fois sur godbolt) la version Mp11 prend environ deux fois plus de temps à compiler. Je ne sais pas quelle part de ce surcoût analyse l'en-tête de boost lui-même et combien instancie les modèles ...
Max Langhof
1
@MaxLanghof Bien sûr. 1,5x si vous n'incluez que algorithm.hppMp11. Et même dans ce cas, nous parlons de 0,08 s contre 0,12 s. Je dois tenir compte du temps qu'il m'a fallu pour écrire cela aussi.
Barry
8
@Barry: Du point de vue de l'ingénierie logicielle, avec vous à 100%. Il y a aussi la facilité de lecture par rapport à une approche manuelle. De plus, peu ou pas de tests sont nécessaires pour garantir l'exactitude de la solution de bibliothèque. Globalement, moins de code et une plus grande confiance entraîneront une baisse des coûts de maintenance tout au long de sa durée de vie.
AndyG
Je suis d'accord, c'est assez simple, mais malheureusement, il existe des équipes qui froncent les sourcils sur le boost.
themagicalyang
il y a des équipes qui froncent les sourcils sur tout. ce n'est pas une raison pour ne pas l'utiliser.
Tomaz Canabrava
13

Ok, j'ai compris. Ce n'est pas joli mais ça marche:

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

J'ai laissé mes propres static_asserttests là-bas pour ... Eh bien, j'espère qu'ils vous aideront.

De plus, je suis sûr qu'il doit y avoir une meilleure solution. Mais c'était le chemin évident "Je sais que cela mènera finalement au but". J'ai finalement dû recourir à l'ajout de a concatou de sortes, je suis sûr qu'il pourrait être utilisé beaucoup plus tôt pour éviter la plupart des cruches.

Max Langhof
la source
4
Programmation de modèles que je peux suivre. C'est génial. J'ai appris quelque chose aujourd'hui.
Jerry Jeremiah
add prend deux listes de types. Comment passez-vous plusieurs listes de types à ajouter dans concat?
themagicalyang
@themagicalyang Bien repéré, c'est un bug (que les tests n'ont pas trouvé car toutes les listes impliquées n'étaient que de longueur 2). Le ...doit aller à l'intérieur de l' concatappel récursif , pas à l'extérieur. Réponse (y compris les cas de test) corrigée. Prouve que Barry a raison concernant les attentes en matière d'exactitude :)
Max Langhof
L'appel produit cartésien à multiplier_all n'est-il pas fondamentalement un multiple_one?
themagicalyang
@themagicalyang No. cartesian_productimplémente la récursivité. multiply_allfait un multiply_onepour chaque liste de types dans le TLspack. cartesian_product::typeest une liste de listes de types. multiply_allprend une liste de types et une liste de listes de types. multiply_oneprend deux listes de type a1, a2, a3et b1, b2, b3et crée a1, b1, b2, b3, a2, b1, b2, b3, a3, b1, b2, b3. Vous avez besoin de ces deux niveaux de déduction ( multiply_all, multiply_one) parce que vous devez descendre deux niveaux de "variadicness", voir mon premier commentaire sur la question.
Max Langhof
9

Repliez les expressions à la rescousse

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{}) ^ ...));

Et tu as fini. Cela présente l'avantage supplémentaire par rapport à la récursivité d'avoir une profondeur d'instanciation 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>);
Passer par
la source
Cela m'intrigue. Existe-t-il un moyen de le représenter comme TL1 * TL2 * TL3 = résultat crossporduct?
themagicalyang
@themagicalyang Qu'entendez-vous par "résultat produit croisé"?
Passer
fondamentalement au lieu de using result = product_t<t1,t2,t3>... une façon de le représenter comme using result = decltype(t1{} * t2{} * t3{});. Hmm, eh bien maintenant qu'il y pense, puisque decltypec'est inévitable, simplement utiliser l'alias que vous avez donné est plus intuitif.
themagicalyang
Intéressant! L'utilisation de la surcharge de l'opérateur vous donne des expressions de pli au lieu des récursions que j'ai dû faire. Le rend également beaucoup plus concis. Je m'en souviendrai la prochaine fois!
Max Langhof
@PasserBy Tous ces opérateurs et fonctions d'assistance doivent-ils se trouver dans le même espace de noms? Je rencontre des problèmes avec tout mettre dans un espace de noms et accéder à product_t en utilisant un alias depuis un espace de noms extérieur.
themagicalyang