Làm cách nào tôi có thể tạo một cách sản phẩm Cartesian của danh sách loại trong C ++?


26

Tự giải thích.

Về cơ bản, giả sử tôi có danh sách loại như vậy:

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

Chúng có thể là số lượng của danh sách loại.

Làm thế nào để tôi có được một kiểu chữ của sản phẩm Cartesian?

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

Tôi đã nghiên cứu cách tạo ra một sản phẩm Cartesian hai chiều như được đưa ra ở đây: Làm thế nào để tạo ra sản phẩm Cartesian của một danh sách loại? , nhưng cách n dường như không quá tầm thường.

Hiện tại tôi đang cố gắng ...

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

Tôi sẽ chỉ nói rằng xem xét mức độ khó để làm cho đúng, chỉ cần sử dụng boost như trong câu trả lời của Barry. Thật không may, tôi phải bị mắc kẹt với một cách tiếp cận bằng tay vì sử dụng boost hay không là một quyết định đến từ một nơi khác :(


8
Ôi, bạn là kẻ háu ăn vì bị trừng phạt 😏
Các cuộc đua nhẹ nhàng trong quỹ đạo

Tôi rất thích nó, nhưng bạn có thể sửa đổi sản phẩm cartes 2 chiều theo cách: 1) typelist đầu tiên thực sự là một typelist của typelists 1 loại; 2) thay vì nối hai loại từ máy đánh chữ, siêu dữ liệu sẽ nối các loại từ danh sách thứ hai vào danh sách "con" của máy đánh chữ thứ nhất (theo cách của cartesian)? Nếu nó khả thi, vấn đề có thể được giải quyết dễ dàng bằng thuật toán đệ quy.
smitsyn

1
Khó khăn thực sự trong việc thực hiện đệ quy cartesian_productlà một danh sách các danh sách loại và tại mỗi bước đệ quy bạn muốn nối các công cụ vào từng danh sách loại bên trong. Bước vào cấp độ đóng gói thứ hai của gói đó sẽ bị trừ đi ...
Max Langhof

1
Tôi đoán bạn cũng có thể thực hiện nó một cách "tuyến tính" bằng cách xem đây là "không gian loại" N chiều, nơi bạn muốn đi qua từng "điểm lưới loại". Bạn tính toán số lượng điểm lưới, sau đó bạn chỉ cần di chuyển ngang qua một mảng ND phẳng và tính toán các loại tại mỗi điểm lưới. Một cái gì đó để xem xét ...
Max Langhof

1
@MaxLanghof Một cái gì đó dọc theo dòng " Một sản phẩm cartesian của tuples trong C ++ 17 "?
Ded repeatator

Câu trả lời:


14

Với Boost.Mp11 , đây là một lớp lót ngắn (như mọi khi):

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

Bản demo .


1
Holy cow ... Nhưng tôi cảm thấy bắt buộc phải chỉ ra rằng (lấy mẫu mỗi mã nhiều lần trên godbolt), phiên bản Mp11 mất khoảng gấp đôi thời gian để biên dịch. Không chắc chắn bao nhiêu chi phí đó đang phân tích cú pháp tiêu đề tăng và bao nhiêu là bắt đầu các mẫu ...
Max Langhof

1
@MaxLanghof Chắc chắn rồi. 1,5 lần nếu bạn chỉ bao gồm algorithm.hppthay vì tất cả Mp11. Và thậm chí sau đó chúng ta đang nói 0,08 giây so với 0,12s. Phải tính đến việc tôi mất bao lâu để viết bài này.
Barry

8
@Barry: Từ quan điểm kỹ thuật phần mềm, với bạn 100%. Ngoài ra còn có cách dễ dàng để đọc so với cách tiếp cận bằng tay. Ngoài ra, có ít hoặc không cần kiểm tra để đảm bảo tính chính xác của giải pháp thư viện. Nhìn chung, ít mã hơn và độ tin cậy cao hơn sẽ dẫn đến chi phí bảo trì thấp hơn cho vòng đời của nó.
AndyG

Tôi đồng ý rằng điều này khá đơn giản nhưng thật không may, có những đội đang cau mày tăng tốc.
themagicalyang

có những đội cau mày trên tất cả mọi thứ. Đây không phải là một lý do để không sử dụng nó.
Tomaz Canabrava

13

OK đã nhận nó. Nó không đẹp nhưng nó hoạt động:

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

Tôi đã để lại static_assertcác bài kiểm tra của riêng mình trong đó cho ... Chà, tôi hy vọng họ sẽ giúp.

Ngoài ra, tôi chắc chắn phải có một giải pháp đẹp hơn. Nhưng đây là con đường rõ ràng "Tôi biết điều này cuối cùng sẽ dẫn đến mục tiêu". Cuối cùng tôi đã phải dùng đến việc thêm một concathoặc sắp xếp, tôi chắc chắn rằng nó có thể được sử dụng sớm hơn nhiều để bỏ qua phần lớn hành trình.


4
Lập trình mẫu mà tôi có thể làm theo. Thật tuyệt vời. Tôi đã học được một cái gì đó ngày hôm nay.
Jerry Jeremiah

add mất hai type_lists. Làm thế nào bạn vượt qua nhiều danh sách loại để thêm vào concat?
themagicalyang

@themagicalyang Phát hiện tốt, đó là một lỗi (mà các bài kiểm tra không tìm thấy vì tất cả các danh sách liên quan chỉ dài 2). Các ...có để đi bên trong đệ quy concatgọi, không bên ngoài. Trả lời (bao gồm cả trường hợp kiểm tra) sửa chữa. Chứng minh Barry đúng về những kỳ vọng chính xác :)
Max Langhof

Không phải cuộc gọi sản phẩm của cartesian đến Multiply_all về cơ bản là nhiều_one sao?
themagicalyang

@themagicalyang Số cartesian_productthực hiện đệ quy. multiply_allkhông một multiply_onecho mỗi danh sách loại trong TLsgói. cartesian_product::typelà một danh sách các danh sách loại. multiply_allcó một danh sách loại và một danh sách các loại danh sách. multiply_onemất hai danh sách loại a1, a2, a3b1, b2, b3và tạo ra a1, b1, b2, b3, a2, b1, b2, b3, a3, b1, b2, b3. Bạn cần hai mức khấu trừ ( multiply_all, multiply_one) này bởi vì bạn cần phải hạ xuống hai cấp độ "biến động", hãy xem nhận xét đầu tiên của tôi về câu hỏi.
Max Langhof

9

Gấp các biểu thức để giải cứu một lần nữa

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

Và bạn đã hoàn thành. Điều này có lợi ích bổ sung so với đệ quy có độ sâu khởi tạo 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>);

Điều này hấp dẫn tôi. Có cách nào để biểu diễn nó dưới dạng kết quả TL1 * TL2 * TL3 = crossporduct không?
themagicalyang

@themagicalyang Bạn có ý nghĩa gì bởi "kết quả sản phẩm chéo"?
qua đường

về cơ bản thay vì using result = product_t<t1,t2,t3>... một số cách để thể hiện nó như using result = decltype(t1{} * t2{} * t3{});. Hmm, bây giờ nó nghĩ về nó, vì decltypekhông thể tránh khỏi, chỉ cần sử dụng bí danh như bạn đã đưa ra là trực quan hơn.
themagicalyang

Hấp dẫn! Sử dụng quá tải toán tử cung cấp cho bạn các biểu thức gấp thay vì các lần thu hồi tôi phải làm. Cũng làm cho nó ngắn gọn hơn nhiều. Tôi sẽ ghi nhớ nó cho lần tới!
Max Langhof

@PasserBy Có phải tất cả các toán tử và hàm của trình trợ giúp cần phải nằm trong cùng một không gian tên không? Tôi đang gặp vấn đề với việc đặt mọi thứ trong một không gian tên và truy cập vào sản phẩm bằng cách sử dụng bí danh từ không gian tên bên ngoài.
themagicalyang
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.