C ++如何通过可选的typedef选择类,使用SFINAE或其他

时间:2012-08-09 00:53:45

标签: c++ templates visual-c++ c++11 sfinae

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

我怀疑使用新的C ++ 11功能有一种优雅的方法可以做到这一点,但我想要空洞了。我有一个解决方案,基于SFINAE的维基百科条目。这很难看。对于每个新的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; 
}

3 个答案:

答案 0 :(得分:3)

您并不需要为每个嵌套类型提供复杂的特征,这可以更简单一些。 Here就是一个例子:

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

答案 1 :(得分:0)

正如大卫的answer所示,宏将有助于减少样板代码。在我看来,大卫的方法是优越的,但对于新的元程序员来说这可能是令人生畏的。因此,我想通过提供可能稍微更明确的潜在替代方案来帮助清理一些kludger。


当无案例不是模板时,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)); 
};

在此example中:

  • 检查是的情况,C被推断为int,导致替换总是以int::H失败。在尝试使用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)); 
};

here所示,编译失败,因为找不到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)); 
};

在此example中,0的双重表示确实起作用。

  • 在是的情况下,0被检查为T::H*的有效初始化值。
  • 在无案例中,0int

为了删除这种双重表示,将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)); 
};

在此example中,可以使用任何整数调用test<T>()

  • 在是的情况下,1intC::H*明确设置为NULL
  • 在无案例中,1也是int

因为sizeof正在评估表达式而不执行它,所以可以避免将编译时已知的值传递给test,如this示例所示。

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了解该方法,那么稍微更明确的方法应该不难理解。

答案 2 :(得分:0)

除了已在此处发布的好方法之外,另一种方法是使用很少使用的dominance in virtual inheritance feature。我正在展示两种可自定义类型的解决方案,可以扩展为任意数量的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