C ++でタイプリストの方法デカルト積を作成するにはどうすればよいですか?


26

自明です。

基本的に、次のようなタイプのリストがあるとします。

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

タイプリストの可変数にすることができます。

デカルト積のタイプリストを取得するにはどうすればよいですか?

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

私はここに与えられているように双方向のデカルト積を作成する方法について手を出しました:タイプリストのデカルト積を作成する方法?、しかしnウェイはそれほど簡単ではないようです。

とりあえずやっています...

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

正しく理解するのがどれほど難しいかを考えると、バリーの答えのようにブーストを使用するだけです。残念ながら、ブーストを使用するかどうかは別の場所からの決定であるため、手作業でのアプローチに悩まされなければなりません:(


8
OOF、あなたが😏罰のために大食家だ
軌道上での明度レース

私はそれでちょっとおしゃべりですが、次のような方法で2方向デカルト積を変更できますか?1)最初のタイプリストは、実際には1タイプのタイプリストのタイプリストです。2)タイプリストの2つのタイプを連結する代わりに、メタ関数は2番目のリストのタイプを最初のタイプリストの「子」リストに追加します(デカルト積の方法)?可能であれば、再帰的アルゴリズムで問題を簡単に解決できます。
smitsyn

1
再帰的な実装の実際の難しさはcartesian_product、型リストのリストであり、再帰の各ステップで、各内部型リストに要素を追加したいということです。パックの2番目のパッキングレベルに入るには、いくつかの控除が必要です...
Max Langhof

1
これを、各「タイプグリッドポイント」をトラバースするN次元の「タイプスペース」と見なすことで、「線形」に実装することもできると思います。グリッド点の数を計算してから、平坦化されたND配列を通過するようにトラバースし、各グリッド点でタイプを計算します。考慮すべきこと...
Max Langhof

1
@MaxLanghof「C ++ 17のタプルのデカルト積」に沿った何か?
Deduplicator

回答:


14

ではBoost.Mp11、これは(いつものように)短いワンライナーです。

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

デモ


1
聖なる牛...しかし、(godboltで各コードを数回サンプリングする)Mp11バージョンのコンパイルには約2倍の時間がかかることを指摘しなければなりません。そのオーバーヘッドのどの程度がブーストヘッダー自体の解析であり、テンプレートのインスタンス化がどの程度であるかは不明です...
Max Langhof

1
@MaxLanghofもちろん。algorithm.hppMp11のすべてではなく、含めるだけの場合は1.5倍。そして、それでも、0.08秒と0.12秒を話している。私もこれを書くのにどれくらい時間がかかったかを考慮に入れなければなりません。
バリー

8
@Barry:ソフトウェアエンジニアリングの観点からは、100%です。また、手作業によるアプローチと比較して、これがどれほど読みやすいかという点もあります。また、ライブラリソリューションの正確性を保証するために必要なテストはほとんどありません。全体的にコードが少なく信頼性が高いほど、そのライフタイムのメンテナンスコストが低くなります。
AndyG

私はこれが非常に単純であることに同意しますが、残念ながらブーストで眉をひそめるチームが存在します。
themagicalyang

すべてに眉をひそめるチームがあります。これはそれを使用しない理由ではありません。
Tomaz Canabrava

13

はい、わかった。それはきれいではありませんが、うまくいきます:

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

static_assertはそこに自分のテストを残しました...まあ、私は彼らが助けてくれることを願っています

また、もっと良い解決策が必要だと私は確信しています。しかし、これは「これが最終的に目標につながることを知っている」という明白な経路でした。私は最終的にconcatorまたはsorts を追加することに頼らざるを得ませんでした。私はそれをはるかに早く使用して、残骸のほとんどをスキップできると確信しています。


4
フォローできるテンプレートプログラミング。それは素晴らしいです。今日は何かを学びました。
ジェリージェレミア

addは2つのtype_listを取ります。連結に追加する複数の型リストをどのように渡していますか?
themagicalyang

@themagicalyangよく知られている、それはバグです(テストでcosに含まれるすべてのリストが長さ2だけであることがわかりませんでした)。...再帰の内側に行かなければならconcatない外、コール。回答(テストケースを含む)が修正されました。正しさの期待に関するバリーの権利を
証明する

デカルト積はmultiply_allを基本的にmultiple_oneに呼び出しませんか?
themagicalyang

@themagicalyang No. cartesian_productは再帰を実装しています。multiply_allmultiply_oneで各タイプのリストについては、TLsパック。cartesian_product::typeタイプリストのリストです。multiply_all型リストと型リストのリストを取ります。multiply_one2種類のリストを取るa1, a2, a3b1, b2, b3して作成しa1, b1, b2, b3a2, b1, b2, b3a3, b1, b2, b3。「変動」の2つのレベルを下げる必要があるためmultiply_all、これらの2つのレベルの控除(、multiply_one)が必要です。質問に対する最初のコメントを参照してください。
Max Langhof

9

表情を折りたたんでもう一度助けて

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

これで完了です。これには、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>);

これは私に興味をそそられます。それをTL1 * TL2 * TL3 = crossporductの結果として表す方法はありますか?
themagicalyang

@themagicalyang「クロスプロダクトの結果」とはどういう意味ですか?
通過者

基本的にはなく、using result = product_t<t1,t2,t3>...として、それを表現するいくつかの方法using result = decltype(t1{} * t2{} * t3{});。うーん、よく考えdecltypeてみると、避けられないので、単に指定したエイリアスを使用する方が直感的です。
themagicalyang

面白い!演算子のオーバーロードを使用すると、私がしなければならなかった再帰の代わりに、式を畳むことができます。また、より簡潔になります。次回も覚えておきます!
Max Langhof

@PasserByこれらのヘルパー演算子と関数はすべて同じ名前空間にある必要がありますか?名前空間内にすべてを配置し、名前空間の外部からエイリアスを使用してproduct_tにアクセスすると問題が発生します。
themagicalyang
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.