ฉันจะสร้างวิธีที่ประเภทรายการคาร์ทีเซียนใน 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{});
    }
}

ฉันจะบอกว่าการพิจารณาว่ามันยากแค่ไหนที่จะทำให้ถูกต้องให้ใช้การกระตุ้นเหมือนคำตอบของ Barry น่าเสียดายที่ฉันต้องติดอยู่กับวิธีการรีดด้วยมือเพราะใช้การเพิ่มหรือไม่เป็นการตัดสินใจที่มาจากที่อื่น :(


8
Oof คุณเป็นคนตะกละสำหรับการลงโทษ😏
Lightness Races ใน Orbit

ฉันชอบที่จะดูดมัน แต่คุณสามารถดัดแปลงผลิตภัณฑ์คาร์ทีเซียนแบบ 2 ทางในแบบที่: 1) นักพิมพ์ดีดคนแรกจริงๆแล้วเป็นผู้พิมพ์ดีดของนักพิมพ์ดีดประเภท 1; 2) แทนที่การเรียงสองประเภทจากนักพิมพ์ดีด metafunction จะต่อท้ายประเภทจากรายการที่สองไปยังรายการ "เด็ก" ของนักพิมพ์ดีดคนแรก (ในรูปแบบผลิตภัณฑ์คาร์ทีเซียน) หากเป็นไปได้ปัญหาสามารถแก้ไขได้อย่างง่ายดายด้วยอัลกอริทึมแบบเรียกซ้ำ
smitsyn

1
ความยากลำบากที่แท้จริงในการใช้งานแบบเรียกซ้ำคือcartesian_productรายการลิสต์ประเภทและในแต่ละขั้นตอนการเรียกซ้ำคุณต้องการผนวกข้อมูลเข้ากับลิสต์แต่ละประเภทภายใน การเข้าสู่ระดับการบรรจุหีบห่อที่สองนั้นจะลดหย่อน ...
Max Langhof

1
ฉันเดาว่าคุณสามารถใช้ "เชิงเส้น" ได้โดยดูที่นี่เป็นพื้นที่พิมพ์ "N-dimension" ที่คุณต้องการสำรวจ "จุดกริดประเภท" แต่ละจุด คุณคำนวณจำนวนของจุดกริดจากนั้นคุณก็สำรวจมันเหมือนคุณจะผ่านอาร์เรย์ ND แบนและคำนวณประเภทที่จุดแต่ละจุด สิ่งที่ต้องพิจารณา ...
Max Langhof

1
@ MaxLanghof มีบางอย่างตามรายการ " ผลิตภัณฑ์คาร์ทีเซียนของ tuples ใน C ++ 17 " หรือไม่?
Deduplicator

คำตอบ:


14

ด้วยBoost.Mp11นี่เป็นหนึ่งซับสั้น (เช่นเคย):

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

การสาธิต


1
วัวศักดิ์สิทธิ์ ... แต่ฉันรู้สึกว่าจำเป็นต้องชี้ให้เห็นว่า (สุ่มตัวอย่างแต่ละรหัสใน godbolt หลายครั้ง) รุ่น Mp11 ใช้เวลาในการรวบรวมประมาณสองเท่า ไม่แน่ใจว่าค่าใช้จ่ายในส่วนนั้นแยกวิเคราะห์ส่วนหัวของตัวเองเพิ่มขึ้นเท่าใดและเทมเพลตการสร้างอินสแตนซ์นั้นเท่าไหร่ ...
Max Langhof

1
@ MaxLanghof แน่นอน 1.5x หากคุณรวมเท่านั้นalgorithm.hppแทนที่จะเป็น Mp11 ทั้งหมด และแม้กระทั่งตอนนี้เรากำลังพูดถึง 0.08s กับ 0.12s ต้องคำนึงถึงระยะเวลาที่ฉันเขียนมันด้วย
Barry

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การทดสอบของตัวเองที่นั่น ... ฉันหวังว่าพวกเขาจะช่วย

นอกจากนี้ฉันแน่ใจว่าจะต้องมีวิธีการแก้ปัญหาที่ดีกว่า แต่นี่เป็นเส้นทางที่ชัดเจนว่า "ฉันรู้ว่านี่จะนำไปสู่เป้าหมาย" ในที่สุดฉันก็ต้องหันไปเพิ่มconcatหรือเรียงลำดับฉันแน่ใจว่ามันสามารถใช้ได้เร็วกว่าที่จะข้าม cruft เกือบทั้งหมด


4
การเขียนโปรแกรมเทมเพลตที่ฉันสามารถติดตามได้ ที่น่ากลัว. ฉันเรียนรู้บางอย่างในวันนี้
Jerry Jeremiah

เพิ่มใช้เวลาสอง type_lists คุณจะผ่านรายการหลายประเภทเพื่อเพิ่มในการเชื่อมโยงได้อย่างไร
themagicalyang

@themagicalyang เห็นดีนั่นเป็นข้อผิดพลาด (ซึ่งการทดสอบไม่พบรายการที่เกี่ยวข้องทั้งหมดมีความยาวเพียง 2) ...ได้ไปภายใน recursive concatโทรไม่ได้ออกไปข้างนอก แก้ไขคำตอบ (รวมถึงกรณีทดสอบ) แล้ว พิสูจน์แบร์รี่ที่ถูกต้องเกี่ยวกับความคาดหวังของความถูกต้อง :)
แม็กซ์ Langhof

การเรียกใช้ผลิตภัณฑ์คาร์ทีเซียนเพื่อเพิ่มจำนวนทวีคูณเป็นทวีคูณหรือไม่
themagicalyang

@themagicalyang ไม่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) เนื่องจากคุณต้องลด "ความแตกต่าง" สองระดับลงไปดูความคิดเห็นแรกของฉันในคำถาม
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 คุณหมายถึงอะไรโดย "cross product result"?
สัญจรภายใน

พื้นแทน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.