2

用户通过定义指定所需选项的类来定制库模板类。称其为清单。这个想法是在清单中有可选的 typedef。例如,如果用户的清单包含 H 的 typedef,我希望库代码使用指示的类型作为其“H”。如果用户清单中没有 typedef,则库将使用默认值。

我怀疑有一种优雅的方法可以利用新的 C++11 特性来做到这一点,但我的想法是空的。我有一个基于 SFINAE 的 Wikipedia 条目的解决方案。这是丑陋的。对于每个新的 H ,它都需要一个新的模板函数has_typedef_H。我隐约感到不安的是,它利用了0可以表示整数或空指针的属性。只是看起来太笨拙了。

有没有更好的办法?最好是可以在 VC++ 2010 中使用的?

在玩具示例中,有五个类,H1、H2 和 U0、U1 和 U2。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,您可以不用额外的样板文件,直接在目标类型上使用 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

除了这里已经发布的好方法之外,另一种方法是使用虚拟继承功能中很少使用的优势。我展示了两种可定制类型的解决方案,可以扩展为任意数量的 N(受基类编译器数量限制):

#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 回答
0

正如大卫的回答所表明的那样,宏将有助于减少样板代码。在我看来,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)); 
};

在这个例子中:

  • yes-caseC被推导为 进行检查int,导致替换总是以 失败int::H。这发生在尝试0用作表示空指针的值之前,因为函数签名仍在尝试识别。
  • no-case 的可变参数为int. 这总是被选中,因为是的情况总是失败。

当 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)。由于 no-case 不能进行推导,因此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 回答