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

나는 그것을 올바르게 얻는 것이 얼마나 어려운지를 고려할 때 Barry의 대답 에서처럼 부스트를 사용하십시오. 불행히도 부스트를 사용하거나 사용하지 않는 것이 다른 곳에서 오는 결정이기 때문에 수동 롤 접근 방식을 고수해야합니다.


8
Oof, 당신은 처벌을위한 열성입니다 😏
Orbit in

나는 그것에 약간 짜증나지만, 당신은 다음과 같은 방식으로 2-way 직교 곱을 수정할 수 있습니다 : 1) 첫 번째 typelist는 실제로 1 type의 typelist입니다. 2) 유형 목록에서 두 유형을 연결하는 대신, 메타 기능은 두 번째 목록에서 유형을 첫 번째 유형 목록의 "자식"목록에 추가합니다 (카르테 시안-제품 방식)? 가능하다면 재귀 알고리즘으로 문제를 쉽게 해결할 수 있습니다.
smitsyn

1
재귀 구현의 실제 어려움 cartesian_product은 유형 목록 목록이며 각 재귀 단계에서 각 내부 유형 목록에 항목을 추가하려고한다는 것입니다. 포장의 두 번째 포장 수준에
들어가려면

1
이것을 각 "유형 그리드 포인트"를 가로 지르고 자하는 N 차원 "유형 공간"으로 보아서 "선형으로"구현할 수있을 것 같습니다. 그리드 포인트의 수를 계산 한 다음 평평한 ND 배열을 통해처럼 그리드 포인트를 가로 질러 각 그리드 포인트에서 유형을 계산합니다. 고려할 사항 ...
Max Langhof

1
@MaxLanghof " C ++ 17에서 튜플의 데카르트 곱 "라인을 따라 뭔가 ?
중복 제거기

답변:


14

Boost.Mp11을 사용하면 이것은 항상 하나의 짧은 라이너입니다.

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

데모 .


1
성스러운 암소 ... 그러나 Mp11 버전은 컴파일하는 데 약 두 배가 걸린다는 것을 지적해야합니다. 얼마나 많은 오버 헤드가 부스트 헤더 자체를 구문 분석하고 템플릿을 인스턴스화하고 있는지 확실하지 않습니다 ...
Max Langhof

1
@MaxLanghof는 확실합니다. algorithm.hppMp11 대신 모두 포함 하는 경우 1.5x 그리고 심지어 우리는 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또는 종류 를 추가하는 것에 의지 해야했다.


4
내가 따라갈 수있는 템플릿 프로그래밍. 대단해. 나는 오늘 무언가를 배웠다.
Jerry Jeremiah

add에는 두 개의 type_list가 필요합니다. concat에 추가하기 위해 여러 유형 목록을 어떻게 전달합니까?
themagicalyang

@ themagicalyang 잘 발견, 그것은 버그입니다 (테스트에서 cos가 관련된 모든 목록이 길이 2에 불과하다는 것을 발견하지 못했습니다). 는 ...재귀 내부에 가야 concat하지 외부 전화. 답변 (테스트 사례 포함)이 수정되었습니다. 정확성 기대와 관련하여 배리의 권리를 입증 :)
Max Langhof

직교 곱이 multiply_all을 기본적으로 multiple_one로 호출하지 않습니까?
themagicalyang 2014

@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 = crossporduct 결과로 나타내는 방법이 있습니까?
themagicalyang

@themagicalyang "교차 결과"는 무엇을 의미합니까?
통행인

기본적으로 using result = product_t<t1,t2,t3>...로 표현하는 방법 using result = decltype(t1{} * t2{} * t3{});입니다. 흠, 이제 그것에 대해 생각 했으므로 decltype피할 수 없으므로 단순히 별칭을 사용하는 것이 더 직관적입니다.
themagicalyang

흥미 롭습니다! 연산자 오버로딩을 사용하면 내가해야하는 재귀 대신 폴드 표현식이 제공됩니다. 또한 훨씬 간결합니다. 다음에 염두에 두겠습니다!
맥스 랭 호프

@PasserBy 모든 도우미 연산자와 함수가 동일한 네임 스페이스에 있어야합니까? 네임 스페이스 안에 모든 것을 넣고 외부 네임 스페이스의 별칭을 사용하여 product_t에 액세스하는 데 문제가 있습니다.
themagicalyang
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.