【问题标题】:C++ How to select class via optional typedef, using SFINAE or whateverC++ 如何通过可选的 typedef 选择类,使用 SFINAE 或其他
【发布时间】:2012-08-09 00:53:44
【问题描述】:

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

我怀疑有一种优雅的方法可以利用新的 C++11 特性来做到这一点,但我的想法是空的。我有一个解决方案,基于 SFINAE 的 Wikipedia 条目。这是丑陋的。它需要一个新的模板函数 has_typedef_H 用于每个新 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" <&LT std::endl;} }; struct H2{ void operator() (){ std::cout <&LT "H2" <&LT 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; }

【问题讨论】:

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


    【解决方案1】:

    您实际上并不需要为每个嵌套类型提供一个复杂的特征,这可以更简单一些。 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 );
    }
    

    【讨论】:

    • 我想到了这两种解决方案。我不太热衷于从一类默认值中推导出来,因为在典型情况下,用户甚至不需要知道这些东西。 “清单”将只是一个函数对象,它实现了他想要最小化的多元函数。自从我使用#macros 以来已经很长时间了,我忘记了如何将字符串拼凑在一起。 :-) 在将下一组很酷的特性添加到 C++(C++25?)之前,这可能是最好的。甚至在之后。
    • 另一种解决方案,也许是最好的解决方案是使用 type_traits 方法。那是非侵入性的。但这个答案并不能解决这个难题。我喜欢拼图。
    • @JiveDadson:第一种方法 type_traits 的做法。类型被传递给提取特征的函数,无论是默认类型还是嵌套类型......在等待新标准时,如果 static if 向前移动,这可能是一个选项,但是关于这个特殊问题,我认为除了更简单的语法之外不会添加任何东西。在您的问题中使用 static if 可以解决的所有问题(我相信)都可以通过滥用继承来解决。
    • 啊哈。光在邓德黑德上空。第一个版本确实避免了将 0 用于 int 和空指针以及其他 kluge 的混乱,并且它已经完成了。宏是老派,但我也是。我想我已经说服自己不能避免宏,因为除非在 sfinae 批准的地方,否则不敢输入 T::H。我想你已经赢得了梦寐以求的复选标记。
    【解决方案2】:

    正如 David 的 answer 所展示的,宏将有助于减少样板代码。在我看来,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)); 
    };
    

    在这个example

    • 检查是情况,C 被推断为int,导致替换总是以int::H 失败。这发生在尝试使用 0 作为表示空指针的值之前,因为仍在尝试识别函数签名。
    • 使用其可变参数为int 检查 no-case。始终选择此选项,因为 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)); 
    };
    

    正如here 所见,编译失败是因为没有找到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)); 
    };
    

    在这个example 中,0 的双重表示确实发挥了作用。

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

    为了消除这种双重表示,int 参数被添加到是的情况下,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&lt;T&gt;()可以用任意整数调用。

    • 在是的情况下,1int,而C::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 的方法,那么稍微更明确的方法应该不难理解。

    【讨论】:

      【解决方案3】:

      除了这里已经发布的好方法之外,另一种方法是使用很少使用的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
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2018-03-18
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-11-24
        • 1970-01-01
        • 1970-01-01
        • 2022-10-14
        相关资源
        最近更新 更多