2

TLTR : 可変個引数テンプレートのリストに格納されたインデックスによって定義された特定の順序に従って、テンプレート コンテナーから関数の引数にいくつかの配列をマップしたいと思います (問題を定義するより簡単な方法は考えられません)。

配列は使用して格納されvoid*ますが、配列と関数パラメーター間のタイプ セーフはヘルパー クラスによって保証されます。同じヘルパー クラスが、指定されたパラメーター パックを展開し、適切な配列をフェッチし、それらを関数ポインターにバインドして、関数を呼び出す必要があります。これは私が立ち往生しているところです。

詳細: 長い質問とコンパイルされないコードの投稿について事前にお詫びしますが、できるだけ簡潔にしようとしました。

問題は、コンテナーの適切なメンバーをファンクター オブジェクトにマッピングすることにあります。コンテナにはtypelistによって定義された配列のリストがあり、その実装はこれと似ています。

簡単にするために、typelist ヘルパー オブジェクトTLAlg::length<TL>TLAlg::TypeAtが定義されていると仮定し、ユーザーが typelist の長さと N 番目の型にそれぞれアクセスできるようにします。

コンテナー クラスは、型リスト (フィールドと呼ばれる) 内の型ごとに配列を割り当て、これらのバッファーへの不透明なポインターを格納します。特定のフィールド インデックスにアクセスするために、タイプセーフな 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);
};

Functorタイプリストの特定のサブセットを使用して、ボリュームに特定の機能を適用するオブジェクトを実装したいと考えています。配列を直接操作する代わりに、ユーザーは、アクセスしたいフィールドのインデックスのリストと、適用する関数へのポインターを提供します。functor オブジェクトは、正しい引数を設定する責任があります。

型の安全性を高めるために、これらを読み取り専用と読み取り/書き込み ( readconstと not const) の 2 つのリストに分けます。指定された関数プロトタイプは、ファンクター オブジェクトの定義と一致する必要があります。コードは、指定された関数ポインターが引数の定義と正確に一致する場合にのみコンパイルされるため、型の不一致について心配する必要はありません。ファンクターの実装は次のとおりです。

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?
    }
}

ご覧のとおり、2 つのパラメーター パックをコンテナー配列にマップし、それらを関数ポインターにバインドする方法がわからないため、ファンクターは現時点では何もしません...

わかりやすくするために、ファンクター クラスの使用例を次に示します。

// 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::forwardandstd::bindで長い間遊んでみましたが、まだ正しい解決策を得ることができません。typelist を置き換えるstd::tupleことも考えられますが、現在の定義を維持することが望ましいです。

このコードは奇妙で不必要に複雑に見えるかもしれませんが、これらのクラスを使用する意味のある大規模なフレームワークの非常に単純化されたバージョンです。

どんな助けでも大歓迎です。

Yakk の回答の説明:

私はより多くの魔法を使っているので、タイプリストが必要です。たとえば、リストの各要素は、名前を関連付けるために単一のタイプではなくタプルにすることができます。これにより、次のようなきちんとしたコードが可能になります。

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

これが、ファンクターで実装したい種類の操作とどのようにうまく組み合わされているか想像できます。

第二に、ゲッターで混乱した理由がわかりました。最初に言ったように、非常に長い質問にもかかわらず、これはコードの非常に単純化されたバージョンです。実際のプログラムでは、ボリュームは多次元であり、単一の配列にフラット化されるか、多次元配列に割り当てられます。これが、ゲッターが完全な座標を必要とする理由です。これらのゲッターには、さまざまなパラメーターを持ついくつかの実装があります。

最後に、Functor 自体が反復空間を制御し、定義済みのスケルトン (つまり、ステンシル、波面など) を適用するため、関数を適用する要素を知る必要はありません。繰り返しますが、簡単にするために省略しました。

4

1 に答える 1

1

まず、あなたのを書き直しますtype_list

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

18個の引数のハックの代わりに変数型を使用してください。書くことは難しくありませんtype_at<n>::typeindex_of<T>::valueこれらとペアベースの間のマッピング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;
};

明確にするために:operator()a を実行した後にmake_seq<sizeof...(Ts)>::type()ヘルパー関数を呼び出し、それをヘルパー関数に渡し、次に を呼び出しfunc( &get_at<s>(/*n maybe?*/)... )ます。ボブはあなたのおじです

私を混乱させる1つのことはこれです:

// 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必要なのか、少なくともなぜあなたが持っていないのかわかりません:

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

あなたが複数のタイプの配列であるoperator()場合、あなたのファンクターに「このファンクターを適用するインデックス」が欠落していると私は思います。Volume

しかし、「インデックスのパックを作成し、ヘルパー関数を呼び出して、関数呼び出しでパックを展開できるようにする」というトリックが欠けていると強く思います。

于 2013-04-02T04:26:36.607 に答える