Як я можу створити спосіб декартового продукту списків типів у 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>
>;

Я замислювався над тим, як створити двосторонній декартовий продукт, як зазначено тут: Як створити декартовий продукт із списку типів? , але російський шлях, здається, не такий тривіальний.

Поки я намагаюся ...

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
Ой, ти ненажера за покарання 😏
Гонки легкості на орбіті

Я якось смоктав на це, але чи можете ви модифікувати двосторонній декартовий продукт таким чином, що: 1) перший тип списку - це насправді список списків машинописів 1 типу; 2) замість об'єднання двох типів із списків друку, метафункція додала б типи з другого списку до "дочірніх" списків першого списку (декартовим способом-продуктом)? Якщо це можливо, проблему можна легко вирішити за допомогою рекурсивного алгоритму.
smitsyn

1
Справжня складність рекурсивної реалізації полягає в тому, що cartesian_productце список списків типів, і на кожному етапі рекурсії ви хочете додати матеріал до кожного внутрішнього списку типів. Потрапляючи на той другий рівень упаковки, дещо виводиться ...
Макс Лангхоф

1
Я думаю, ви могли також реалізувати це "лінійно", розглядаючи це як N-мірний "простір типу", де ви хочете пройти кожну "точку сітки типу". Ви обчислюєте кількість точок сітки, а потім просто проходите її так, як би ви пройшли через сплющений ND-масив і обчислювали типи в кожній точці сітки. Щось врахувати ...
Макс Ленгоф

Відповіді:


14

З Boost.Mp11 це короткий однолінійний (як завжди):

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

Демо .


1
Свята корова ... Але я відчуваю обов'язок зазначити, що (вибірки кожного коду кілька разів на Godbolt) версія Mp11 займає приблизно вдвічі більше часу. Не впевнений, яка частина цих накладних
розбирає

1
@MaxLanghof Звичайно. 1,5х, якщо ви включаєте лише algorithm.hppзамість усіх Mp11. І навіть тоді ми говоримо 0,08s проти 0,12s. Доведеться враховувати, скільки часу знадобилося мені і для написання цього.
Баррі

8
@Barry: з точки зору інженерії програмного забезпечення, з вами на 100%. Існує також те, як легко це читати проти ручного прокрученого підходу. Крім того, для забезпечення правильності рішення бібліотеки майже не потрібне тестування. Загалом менше коду та вища впевненість призведе до зниження витрат на обслуговування протягом його терміну експлуатації.
AndyG

Я погоджуюсь, що це досить просто, але, на жаль, є команди, які нахмуриться на підсилення.
themagicalyang

є команди, які на все хмуряться. це не привід не використовувати його.
Томаз Канабрава

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тести на ... Ну, я сподіваюся, що вони допоможуть.

Крім того, я впевнений, що має бути приємніше рішення. Але це було очевидним "я знаю, що це врешті призведе до мети". Зрештою, мені довелося вдатися до додавання concatабо сортування, я впевнений, що це можна було використовувати набагато раніше, щоб пропустити більшу частину сукупності.


4
Програмування шаблонів, за якими я можу дотримуватися. Це круто. Я щось сьогодні дізнався.
Джеррі Єремія

add займає два типи списку. Як ви передаєте кілька списків типів, які потрібно додати в стислість
themagicalyang

@themagicalyang Добре помічений, це помилка (тести не знайшли, тому що всі включені списки були лише довжиною 2). ...Мушу йти всередині рекурсивного concatвиклику, а не зовні. Відповідь (включаючи тестові випадки) виправлена. Доводить Баррі право щодо коректних очікувань :)
Макс Ленгоф

Хіба декартовий продукт не закликає до множини_всіх, головним чином, кількох_один?
themagicalyang

@themagicalyang No. cartesian_productреалізує рекурсію. multiply_allробить multiply_oneсписок для кожного списку типів у TLsпакеті. cartesian_product::typeце список списків типів. multiply_allприймає список типів і список списків типів. multiply_oneприймає два списки типу a1, a2, a3і b1, b2, b3та створює a1, b1, b2, b3, a2, b1, b2, b3, a3, b1, b2, b3. Вам потрібні ці два рівні дедукції ( multiply_all, multiply_one), оскільки вам потрібно спуститись вниз на два рівні "варіативності", дивіться мій перший коментар до питання.
Макс Ленгоф

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 = результат перехресного перетворення?
themagicalyang

@themagicalyang Що ви маєте на увазі під результатом перехресного продукту?
Перехожий

в основному замість using result = product_t<t1,t2,t3>... якимось чином це представити як using result = decltype(t1{} * t2{} * t3{});. Хм, а тепер, коли вона думає про це, оскільки decltypeце неминуче, просто використовувати псевдонім, який ви дали, є більш інтуїтивним.
themagicalyang

Цікаво! Використання оператора перевантаження дає змогу складати вирази замість рекурсій, які мені довелося робити. Також робить його набагато більш стислим. Я буду пам'ятати про це наступного разу!
Макс Ленгоф

@PasserBy Чи всі ці допоміжні оператори та функції повинні бути в одному просторі імен? У мене виникають проблеми з введенням всередині простору імен та з доступом до product_t, використовуючи псевдонім із зовнішнього простору імен.
themagicalyang
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.