2

ユーザーは、必要なオプションを指定するクラスを定義することにより、ライブラリテンプレートクラスをカスタマイズします。それをマニフェストと呼びます。マニフェストにオプションのtypedefを含めるという考え方です。たとえば、ユーザーのマニフェストにHのtypedefが含まれている場合、ライブラリコードで指定されたタイプを「H」として使用する必要があります。ユーザーのマニフェストにtypedefがない場合、ライブラリはデフォルトを使用します。

新しいC++11機能を利用してこれを行うためのエレガントな方法があると思いますが、私は空っぽになっています。SFINAEのウィキペディアエントリに基づいた解決策があります。醜いです。新しいHごとに新しいテンプレート関数has_typedef_Hが必要です。0が整数またはnullポインターのいずれかを意味するというプロパティを悪用するのは、漠然と不安です。あまりにもクルージーのようです。

もっと良い方法はありますか?できればVC++2010で動作するものですか?

おもちゃの例では、H1、H2、U0、U1、U2の5つのクラスがあります。H1とH2は、ライブラリクラスLの「ヘルパー」の例です。H1がデフォルトです。Uは、ユーザー定義クラスの例です。この例では、ライブラリクラスLの定義を省略し、main()の本体を使用して、Uのtypedef(またはその欠如)に基づいてHを選択しました。タイプH2の主題。


struct H1{
    void operator() (){ std::cout << "H1" << std::endl;}
};

struct H2{
    void operator() (){ std::cout << "H2" << std::endl;}
};


struct default_H: public H1 {};

struct U2 {
    typedef H2 H;
};

struct U1 {
    typedef H1 H;
};

struct U0 {
};


template <typename T>
class has_typedef_H {

    typedef char no[false+1];
    typedef char yes[true+1];

    template 
    static yes& test(typename C::H*);

    template 
    static no& test(...);

public:
    static const bool value = sizeof(test(0))-1; 
};


template<typename U, bool >
struct type_H_B: public default_H{};

template<typename U>
struct type_H_B<U, true>: public U::H {};

template<typename U>
struct H_type: public type_H_B<U, has_typedef_H<U>::value> {};

int main() {

    H_type<U0> h0;
    H_type<U1> h1;
    H_type<U2> h2;

    // Prints H1 H1 H2
    h0();
    h1();
    h2();
    return 0; 
}

4

3 に答える 3

3

ネストされたタイプのそれぞれに複雑な特性を提供する必要はありません。これは少し簡単に行うことができます。 次に例を示します。

// Helper to map any type to void, needed by SFINAE below
template <typename T>
struct void_type {
    typedef void type;
};

// Selects a nested typedef or a default type D (using a macro to reduce boilerplate):
#define SELECT_NESTED_TYPE( TYPE )                                       \
template <typename T, typename D, typename _ = void>                     \
struct select_##TYPE{                                                    \
    typedef D type;                                                      \
};                                                                       \
template <typename T, typename D>                                        \
struct select_##TYPE<T, D, typename void_type<typename T::TYPE>::type> { \
    typedef typename T::TYPE type;                                       \
};                                                                      

SELECT_NESTED_TYPE( int_t );
SELECT_NESTED_TYPE( float_t );
//...
#undef SELECT_NESTED_TYPE

// Use
template <typename T>
class TheTemplate {
public:
   typedef typename select_int_t<T,int>::type int_t;
   typedef typename select_float_t<T,double>::type float_t;
   //....
};

// Test:
template <typename T, typename U> struct same_type {
   static const bool value = false;
};
template <typename T> struct same_type<T,T> {
   static const bool value = true;
};
struct test1 {
};
struct test2 {
   typedef long long int_t;
   typedef float float_t;
};
int main() {
   // test1 has the default typedefs
   assert(( same_type< TheTemplate<test1>::int_t, int>::value ));
   assert(( same_type< TheTemplate<test1>::float_t, double>::value ));
   // test2 has the ones in the type
   assert(( same_type< TheTemplate<test2>::int_t, long long>::value ));
   assert(( same_type< TheTemplate<test2>::float_t, float>::value ));
}

マクロが使用するデフォルトの型を取り、デフォルトの場合(ネストされた型が定義されていない場合)にそれを注入する場合は、少し単純なソリューションを提供することを選択できます。確かに、これにはネストされたタイプごとにトレイトを作成する必要がありますが、トレイトはほんの数行です(マクロとして定義するのはそれほど難しくありません)。または、潜在的なtypedefが2つしかない場合は、追加の定型文なしで実行して、宛先タイプで直接SFINAEを使用できる可能性があります。


完全に異なるアプローチ...可能であれば

ライブラリで使用されているタイプを変更できる場合は、継承を悪用することで、はるかに単純なソリューションを使用できます(ただし、それほどクールなソリューションではありません)。使用するデフォルトタイプのtypedefのみを保持する基本クラスを作成し、すべてのユーザークラスがデフォルトを提供するクラスから派生するようにします。ユーザーがデフォルトよりも優れたヘルパーを提供したい場合は、typedefを提供する必要があります。typedefが提供されていない場合、ルックアップは階層の上位にあるデフォルトを見つけます。

struct default_helpers {
   typedef Helper1 helper1_t;
   typedef Helper2 helper2_t;
// ...
};
struct user_type_1 : default_helpers {
};
struct user_type_2 : default_helpers {
   typedef MyHelper helper1_t;           // I prefer this one...
};
int main() {
   assert( same_type< user_type1::helper1_t, default_helpers::helper1_t >::value );
   assert( !same_type< user_type2::helper1_t, default_helpers::helper1_t >::value );
   assert( same_type< user_type1::helper2_t, user_type2::helper2_t>::value );
}
于 2012-08-09T03:29:49.607 に答える
0

Davidの答えが示すように、マクロは定型コードを減らすのに役立ちます。私の意見では、Davidのアプローチは優れていますが、新しいメタプログラマーにとっては気が遠くなる可能性があります。そのため、少し明確になる可能性のある代替案を提供することで、クラジャーの一部を片付けるのを手伝いたいと思います。


no-caseがテンプレートでない場合、 :0として扱われます。int

template <typename T>
class has_typedef_H {
  typedef char no[1];
  typedef char yes[2];

  template <typename C>
  static yes& test(typename C::H*);

//  template <typename>
  static no& test(...);

public:
  static const bool value = sizeof(yes) == sizeof(test(0)); 
};

このでは:

  • Cyes-caseは、として推定されてチェックされ、int置換は常に。で失敗しint::Hます。これは0、関数シグネチャがまだ識別されようとしているため、nullポインタを表す値として使用しようとする前に発生します。
  • no-caseは、可変個引数が。である場合にチェックされますint。yes-caseは常に失敗するため、これは常に選択されます。

no-caseがテンプレート関数に変換されるとき、推論するテンプレート引数がないため、no-caseは候補ではありません。

template <typename T>
class has_typedef_H {
  typedef char no[1];
  typedef char yes[2];

  template <typename C>
  static yes& test(typename C::H*);

  template <typename>
  static no& test(...);

public:
  static const bool value = sizeof(yes) == sizeof(test(0)); 
};

ここに見られるように、に一致する関数が見つからないため、コンパイルは失敗しますtest(int)。ノーケースでは控除が利用できないため、test呼び出しでは、使用する専門分野を明示的に選択する必要があります。

template <typename T>
class has_typedef_H {
  typedef char no[1];
  typedef char yes[2];

  template <typename C>
  static yes& test(typename C::H*);

  template <typename>
  static no& test(...);

public:
   static const bool value = sizeof(yes) == sizeof(test<T>(0)); 
};

このでは、0の二重表現が機能します。

  • はいの場合、0はの有効な初期化値としてチェックされますT::H*
  • の場合0は、intです。

この二重表現を削除するために、int引数がyes-caseに追加され、C::H*引数にはデフォルト値の。が提供されますNULL

template <typename T>
class has_typedef_H {
  typedef char no[1];
  typedef char yes[2];

  template <typename C>
  static yes& test(int, typename C::H* = NULL);

  template <typename>
  static no& test(...);

public:
  static const bool value = sizeof(yes) == sizeof(test<T>(1)); 
};

このでは、test<T>()は任意の整数で呼び出すことができます。

  • はいの場合、1は、intであり、C::H*明示的にに設定されNULLます。
  • の場合1は、もintです。

は式を評価し、実行しないため、この例に示すように、sizeofコンパイル時に既知の値をに渡さないようにすることができます。test

template <typename T>
class has_typedef_H {
  typedef char no[1];
  typedef char yes[2];

  template <typename C>
  static yes& test(T*, typename C::H* = NULL);

  template <typename>
  static no& test(...);

  static T* t;

public:
  static const bool value = sizeof(yes) == sizeof(test<T>(t)); 
};

0結局、 SFINAEで使用されているケースはかなり一般的であることがわかりました。そうは言っても、誰かがを使用したアプローチを理解している場合は0、もう少し明確なアプローチを理解するのは難しいことではありません。

于 2012-08-09T13:01:09.720 に答える
0

ここにすでに投稿されている優れたアプローチに加えて、さらに別のアプローチは、仮想継承機能でめったに使用されないドミナンスを使用することです。任意の数のNに拡張できる2つのカスタマイズ可能なタイプのソリューションを示しています(基本クラスのコンパイラーの制限の数に従います)。

#include <stdio.h>

struct A1 {};
struct A2 {};
struct B1 {};
struct B2 {};

struct DefaultManifest {
    typedef A1 A;
    typedef B1 B;
};

template<class T>
struct A_is : virtual DefaultManifest { typedef T A; };

template<class T>
struct B_is : virtual DefaultManifest { typedef T B; };


template<class T> struct Name { static char const* value; };
template<> char const* Name<A1>::value = "A1";
template<> char const* Name<A2>::value = "A2";
template<> char const* Name<B1>::value = "B1";
template<> char const* Name<B2>::value = "B2";

struct na1 : virtual DefaultManifest {};
struct na2 : virtual DefaultManifest {};

template<class T1 = na1, class T2 = na2>
struct Library {
    struct Manifest : T1, T2 {};
    typedef typename Manifest::A A;
    typedef typename Manifest::B B;

    Library() {
        printf("A is %s, B is %s\n", Name<A>::value, Name<B>::value);
    }
};

int main() {
    Library<> lib1;
    Library<A_is<A2> > lib2;
    Library<B_is<B2> > lib3;
    Library<A_is<A2>, B_is<B2> > lib4;
    Library<B_is<B2>, A_is<A2> > lib5;
}

上記の出力:

A is A1, B is B1
A is A2, B is B1
A is A1, B is B2
A is A2, B is B2
A is A2, B is B2
于 2012-08-09T13:43:19.410 に答える