2013-04-01 5 views
2

TLTR : 템플릿 배열의 일부 배열을 가변 배열 템플릿 목록에 저장된 인덱스로 정의 된 특정 순서에 따라 함수의 인수에 매핑하고 싶습니다. (나는 문제를 정의하는 더 간단한 방법을 생각할 수 없다).변수 템플릿과 타입리스트를 사용하여 불투명 한 배열을 함수 인수에 매핑

배열은 void*을 사용하여 저장되지만 배열 사이의 유형 안전성과 함수 매개 변수는 도우미 클래스에 의해 보장됩니다. 동일한 도우미 클래스는 주어진 매개 변수 팩을 확장하고, 적절한 배열을 가져 와서 함수 포인터에 바인딩하고 함수를 호출해야합니다. 이것은 내가 붙어있는 곳이다.

세부 사항 : 오랜 질문과 게시 코드는 미리 사과하지만 최대한 간결하게하려고 노력했습니다.

문제는 컨테이너의 적절한 구성원을 펑터 개체에 매핑하는 것으로 구성됩니다. 컨테이너에는 typelist에 의해 정의 된 배열 목록이 있으며 구현은 this one과 유사합니다.

간단히하기 위해 입력 목록 도우미 객체 TLAlg::length<TL>TLAlg::TypeAt이 정의되어 있고 사용자가 입력 목록 및 N 번째 유형의 길이에 각각 액세스 할 수 있다고 가정합니다.

컨테이너 클래스는 유형 목록 (필드라고 함)의 각 유형에 대해 배열을 할당하고 이러한 버퍼에 불투명 포인터를 저장합니다. typesafe getter는 특정 필드 인덱스에 액세스하기 위해 구현됩니다. 그것의 구현은 다음과 같다 :

// container class, stores an array for each type in the typelist 
template<class TL> 
class Volume { 
public: 
    // list of opaque buffers 
    void *opaque_buffers[TLAlg::length<TL>::value]; 

    template<int size> 
    Volume(const size_t (&dim)[size]){ 
     // each opaque_buffers is initialized here 
     ... 
    } 

    // getters are using the field index for type-safety 
    template <int index> typename 
    TLAlg::TypeAt<TL, index>::Result & 
    get(const std::initializer_list<size_t> &position); 
}; 

우리는 typelist의 특정 부분 집합을 사용하여 볼륨에 주어진 함수를 적용하는 Functor 객체를 구현하려는. 배열을 직접 조작하는 대신 사용자는 액세스하려는 필드의 인덱스 목록과 적용 할 함수의 포인터를 제공합니다. Functor 객체는 올바른 인수를 설정해야합니다.

유형의 안전을 향상시키기 위해, 우리는 두 목록에서 그 분리 : 읽기 전용 및/쓰기 ( const하지 const을 읽기) 참조하십시오. 주어진 함수 프로토 타입은 functor 오브젝트의 정의와 일치해야합니다. 주어진 함수 포인터가 인수 정의와 정확히 일치하는 경우에만 코드가 컴파일되므로 불일치 유형에 대해 걱정할 필요가 없습니다. 펑터의 구현은 다음과 같습니다 당신이 볼 수 있듯이 나는 용기 어레이에 두 개의 매개 변수 팩을지도하고 결합하는 방법을 모르기 때문에

template<typename TL, class T1, class T2> struct Functor{}; 
template< typename TL, 
      template<size_t...> class T1, size_t... A1, // read only arguments 
      template<size_t...> class T2, size_t... A2 // read-write arguments 
     > 
struct Functor< TL, T1<A1...>, T2<A2...> >{ 
    // type of the function pointer 
    typedef void (*Type)(const typename TLAlg::TypeAt<TL, A1>::Result* ... , 
           typename TLAlg::TypeAt<TL, A2>::Result* ...); 

    Functor(Volume<TL> &v, Type f): f(f){ 
     // At this point we have everything we need: the volume, the function 
     // and the list of arguments, but how to combine them all? 

     // maybe some magic here to map the opaque pointers to the arguments? 
    } 

    void operator()(){ 
     // or maybe here? 
    } 
} 

은 펑은 지금 아무것도하지 않습니다 함수 포인터에 ...

명확성을 위해

, 여기에 펑터 클래스에 대한 사용의 예입니다

// main Typelist 
typedef Typelist<float, Typelist<double, Typelist<int, NullType>>> Pixel; 

// function we want to apply: reads first and last field of the list and updates second 
void foo(const float *f1, 
     const int *f3, 
       double *f2){} 

// helper class to store the parameter packs 
template<size_t ... T> struct Pack {}; 

int main(){ 
    // volume declaration 
    Volume<Pixel> volume((size[]){1024,1024}); 

    // delare typesafe functor 
    Functor<Pixel,  // typelist 
      Pack<0,2>, // list of read-only fields 
      Pack<1> // list of read-write fields 
      > apply_foo(volume, foo); 

    apply_foo(); // <- this does nothing at the moment 
} 

나는 오랜 시간 동안 std::forwardstd::bind와 함께 연주 시도는,하지만 난 얻을 수 없다 아직 올바른 해결책은 아닙니다. 유형 목록을 std::tuple으로 대체하는 것이 고려 될 수 있지만 현재 정의를 유지하는 것이 바람직합니다.

이 코드는 이상하고 불필요하게 복잡해 보일지 모르지만 이러한 클래스를 사용하는 것이 막대한 프레임 워크의 매우 단순화 된 버전입니다.

도움을 주시면 감사하겠습니다.Yakk의 답변

부연 설명 :

나는리스트의 각 요소는 튜플 대신 이름을 연결하는 하나의 유형이 될 수 있습니다 예를 들어, 그것은 더 마법을하고있는 중이 야하기 때문에 나는 typelist 필요합니까. 이를 통해 같은 깔끔한 코드 :이 내가 펑터와 구현하려는 작업의 종류에 아주 잘 결합하는 방법을 당신이 상상할 수있는

typedef MakeTypeList((float, p), 
        (float, bnd), 
        (float, gosa)) HimenoFields; 

// I can now use aliases, and the declaration order does not matter in the code. 
// here is an access to the second field: 
volume.get<HimenoFields::bnd>({0,0,0}); 

.

둘째로, 나는 왜 당신이 getter에 혼란스러워하는지 이해합니다. 제가 처음에 말했듯이 이것은 아주 긴 질문에도 불구하고 코드의 매우 단순화 된 버전입니다. 실제 프로그램에서 볼륨은 다차원이며 단일 배열에서 병합되거나 다차원 배열로 할당되므로 getter가 완전한 좌표를 필요로합니다. 이러한 getter에는 여러 가지 매개 변수가있는 여러 구현이 있습니다.

마지막으로 Functor는 반복 영역을 제어하고 사전 정의 된 스켈레톤 (예 : 스텐실, 웨이브 프론트 ...)을 적용하기 때문에 함수를 적용 할 요소를 알 필요가 없습니다. 다시 간단히하기 위해 생략했습니다. variardic 유형 대신하여 18 인수 해킹과

template<typename... Ts> 
struct type_list {}; 

:

+0

TL TR 꽤 있습니다! –

+0

내가 틀렸다면 정정 해주세요.하지만'Functor , Pack <1>> :: Type'은'foo'의 서명과 아주 유사하지 않습니다. 'std :: tuple'을 타입리스트로 즉석에서 볼 때, 당신은 [GCC가 그 주제에 관해 무엇을 말하고 있는지] 볼 수 있습니다 (http://coliru.stacked-crooked.com/view?id=d1ce87766f03b88b3494d6e77dae58fc-50d9cfc8a1d350e7409e81e87c2653ba). –

+0

글쎄, 내 잘못을 발견했다. 인수를 다시 셔플 할 수있는 코드가 더있어 사본 복사에 너무 빠르다. const는 내가 준 예제에서 함께 포장됩니다. 그에 따라'foo'를 편집했습니다. – Thibaut

답변

1

첫째, 당신의 type_list를 다시 작성합니다. type_at<n>::typeindex_of<T>::value을 쓰는 것은 어렵지 않습니다. 이것들과 당신의 pair-based TypeList 사이의 매핑은 어렵지 않습니다.

template<typename list> 
struct make_TypeList; 

template<typename T0, typename T1, typename...Ts> 
struct make_TypeList<type_list<T0, T1, Ts...>> { 
    typedef typename make_Typelist< type_list<T1, Ts...> >::type tail; 
    typedef TypeList< T0, tail > type; 
}; 
template<typename T0> 
struct make_TypeList<type_list<T0>> { 
    typedef TypeList< T0, NullType > type; 
}; 
template<> 
struct make_TypeList<type_list<>> { 
    typedef NullType type; 
}; 

정말로 필요한 경우. 비 형식 목록으로 작업해야하는 이유가 있지만 그 중 아무 것도 시연하지는 않습니다.

컴파일 타임 형식의 인덱스를 빌드하는 것은 약간 까다 롭지 만 상한을 전달하면 할 수 있습니다. 목표는 순서 만드는 것입니다 : 당신이 컴파일시에 이러한 인덱스를 얻는 경우

template<size_t... s> 
struct seq {}; 

을,이 쉽다. 이 순서를 가지고, 당신은 type_at이 있으면, 당신은 종류의이 같은 전화 기능을 쓸 수있다 : 우리는 함수 호출에 직접 순서를 풀고

template<size_t... s, typename... Ts> 
void evaluate(seq<s...> unused, void(*func)(Ts*... ts)) { 
    func(&get_at<s>()...); 
} 

합니다.공교롭게도, 문제가 종종 시퀀스를 쉽게 생성 할 수있는 단지 0,1,2,3,4,...,n-1입니다 :

template<size_t max, size_t... s> 
struct make_seq:make_seq<max-1, max-1, s...> {}; 
template<size_t... s> 
struct make_seq<0, s...> { 
    typedef seq<s...> type; 
}; 

가 명확하게하려면 도우미 함수 make_seq<sizeof...(Ts)>::type()을 수행 한 후 호출 operator(), 전달하는 도우미 함수를 다음 호출로

// getters are using the field index for type-safety 
template <int index> typename 
TLAlg::TypeAt<TL, index>::Result & 
get(const std::initializer_list<size_t> &position); 

나는에 const std::initializer_list<size_t> &position이 필요한 이유를 잘 모르겠어요, 또는 : func(&get_at<s>(/*n maybe?*/)...), 그리고 밥은 삼촌 날이있다 혼란

한 가지입니다 적어도 당신이하지 않는 이유 : 내가 당신이 당신의 펑의 operator()Volume 여러 유형의 배열 인 경우, "이 펑터를 적용 않는 인덱스"없는 것을 생각하게

template <int index> typename 
TLAlg::TypeAt<TL, index>::Result & 
get_at(size_t n); 

합니다.

는하지만 강하게은 "당신이 다음 배짱이 얻을 함수 호출에 팩을 확장 할 수 있도록 인덱스의 팩을, 도우미 함수를 호출"당신이 누락 된 트릭 생각한다.

+0

정교한 답변에 감사드립니다. 내가 언급 한 몇 가지 사항을 명확히하기 위해 내 게시물을 편집했습니다. 나는 오늘 그것을 시험해보고 구현할 수 있다면 대답을 받아 들일 것이다.) – Thibaut

+0

@Thibaut'type_list , type_list < foo >, std :: string>'는 완전히 구조화 된 목록을 허용한다. 'TypeList'처럼. 유일한 차이점은'TypeList'는 tree 기반 타입 구조 (lisp "lists"와 같은)이고, type_list는 선형 기반 타입 구조이며, C++는 "반복"over and "unpacking"을 지원한다는 점입니다 "선형 기반 유형 구조. 반면에 타입 목록이 길이가 1000에 가까우면 C++ 템플릿 메타 프로그래밍의 재귀 제한은 균형있는 2 진 트리 기반 유형 컬렉션이 필요하다는 것을 의미합니다. – Yakk

+0

맞아, 네가 알아챈 것 같아. 그러나 필드에 대한 인덱스는 variadic 템플릿에 저장되어 있습니다. 단, mine은'seq' 대신에'Pack '으로 불리우므로, 쉽게 확장 할 수 있습니다. 유형리스트는 유형 안전을 위해 여기에 있습니다. 유형 목록을 색인 목록에 "정렬"하기 위해 트리 형식 목록을 평면 목록으로 대체해야한다고 말하고 있습니까? – Thibaut