首页 文章

使用SFINAE进行模板类专业化

提问于
浏览
34

假设我有这些声明

template<typename T> class User;
template<typename T> class Data;

并且想要为 T = Data<some_type> 实现 User<> 以及从 Data<some_type> 派生的任何类,但也允许在其他地方定义的其他特化 .

如果我还没有类模板 User<> 的声明,我可以简单地说

template<typename T,
         typename A= typename std::enable_if<is_Data<T>::value>::type>
class User { /*...*/ };

哪里

template<template<typename> data>> struct is_Data
{ static const bool value = /* some magic here (not the question) */; };

但是,这有两个模板参数,因此与前一个声明发生冲突,其中 User<> 仅使用一个模板参数声明 . 还有什么我可以做的吗?

(注意

template<typename T,
         typename A= typename std::enable_if<is_Data<T>::value>::type>
class User<T> { /*...*/ };

不起作用(默认模板参数可能不在部分特化中使用),也不起作用

template<typename T> class User<Data<T>> { /*...*/ };

因为它不允许从 Data<> 派生的类型,也不允许

template<typename T>
class User<typename std::enable_if<is_Data<T>::value,T>::type>
{ /*...*/ };

因为模板参数 T 未用于部分特化 . )

3 回答

  • 5

    既然你说你还在等待更好的答案,这就是我的看法 . 它并不完美,但我认为它会尽可能地使用SFINAE和部分特化 . (我想Concepts会提供一个完整而优雅的解决方案,但我们将不得不等待一段时间 . )

    该解决方案依赖于最近仅在C 14的最终版本之后的标准工作草案中指定的别名模板的功能,但实现已经支持了一段时间 . N4527 [14.5.7p3]草案中的相关措辞是:

    但是,如果template-id是依赖的,则后续模板参数替换仍适用于template-id . [示例:template <typename ...> using void_t = void;
    template <typename T> void_t <typename T :: foo> f();
    ˚F<int>的(); //错误,int没有嵌套类型foo

    • 末端的例子]

    以下是实现此想法的完整示例:

    #include <iostream>
    #include <type_traits>
    #include <utility>
    
    template<typename> struct User { static void f() { std::cout << "primary\n"; } };
    
    template<typename> struct Data { };
    template<typename T, typename U> struct Derived1 : Data<T*> { };
    template<typename> struct Derived2 : Data<double> { };
    struct DD : Data<int> { };
    
    template<typename T> void take_data(Data<T>&&);
    
    template<typename T, typename = decltype(take_data(std::declval<T>()))> 
    using enable_if_data = T;
    
    template<template<typename...> class TT, typename... Ts> 
    struct User<enable_if_data<TT<Ts...>>> 
    { 
        static void f() { std::cout << "partial specialization for Data\n"; } 
    };
    
    template<typename> struct Other { };
    template<typename T> struct User<Other<T>> 
    { 
        static void f() { std::cout << "partial specialization for Other\n"; } 
    };
    
    int main()
    {
        User<int>::f();
        User<Data<int>>::f();
        User<Derived1<int, long>>::f();
        User<Derived2<char>>::f();
        User<DD>::f();
        User<Other<int>>::f();
    }
    

    运行它打印:

    primary
    partial specialization for Data
    partial specialization for Data
    partial specialization for Data
    primary
    partial specialization for Other
    

    正如你所看到的那样,为 DD 选择了's a wrinkle: the partial specialization isn',我们只能说't be, because of the way we declared it. So, why don't

    template<typename T> struct User<enable_if_data<T>>
    

    并允许它匹配 DD ?这实际上在GCC中有效,但由于[14.5.5p8.3,8.4]([p8.3]可能在将来消失,因为它是多余的 - CWG 2033),因此被Clang和MSVC正确拒绝:

    专门化的参数列表不应与主模板的隐式参数列表相同 . 专业化应比主要模板(14.5.5.2)更专业化 .

    User<enable_if_data<T>> 等效于 User<T> (模数替换为该默认参数,单独处理,如上面的第一个引言所解释的),因此是一种无效的部分特化形式 . 不幸的是,匹配像 DD 之类的东西,一般来说,需要一个形式为 T 的部分特化参数 - 我担心我们可以最终说这个部分不能在给定的约束内解决 . (有Core issue 1980,暗示了一些关于模板别名使用的未来规则,但我怀疑它们会使我们的案例有效 . )

    只要从 Data<T> 派生的类本身就是模板特化,使用上述技术进一步约束它们就可以了,所以希望这对你有用 .


    编译器支持(这是我测试的,其他版本也可以工作):

    • Clang 3.3 - 3.6.0, -Wall -Wextra -std=c++11 -pedantic - 如上所述 .

    • GCC 4.7.3 - 4.9.2,相同选项 - 与上述相同 . 奇怪的是,GCC 5.1.0 - 5.2.0不再使用正确版本的代码选择部分特化 . 这看起来像一个回归 . 我没有时间整理一份正确的错误报告;如果你愿意,可以自由地做 . 该问题似乎与参数包与模板模板参数一起使用有关 . 无论如何,GCC使用 enable_if_data<T> 接受不正确的版本,因此这可以是一个临时解决方案 .

    • MSVC:Visual C 2015, /W4 ,如上所述工作 . 旧版本不喜欢默认参数中的 decltype ,但该技术本身仍然有效 - 用另一种表达约束的方式替换默认参数使其适用于2013 Update 4 .

  • 19

    IF User<> 的原始声明可以适应

    template<typename, typename=std::true_type> class User;
    

    然后我们可以找到解决方案(按照Luc Danton的评论,而不是使用std::enable_if

    template<typename>
    struct is_Data : std::false_type {};
    template<typename T>
    struct is_Data<Data<T>> : std::true_type {};
    
    template<typename T>
    class User<T, typename is_Data<T>::type >
    { /* ... */ };
    

    但是,这不能回答原始问题,因为它需要更改 User 的原始定义 . 我还在等待更好的答案 . 这可能是最终证明没有其他解决方案的结果可能 .

  • 9

    由于您只想在单个条件为真时实现它,最简单的解决方案是使用静态断言 . 它不需要SFINAE,如果使用不正确则给出明确的编译错误,并且不需要调整 User<> 的声明:

    template<typename T> class User {
      static_assert(is_Data<T>::value, "T is not (a subclass of) Data<>");
      /** Implementation. **/
    };
    

    另见:When to use static_assert instead of SFINAE? . static_assert 是一个c 11构造,但是有很多可用于pre-c 11编译器的解决方法,例如:

    #define STATIC_ASSERT(consdition,name) \
      typedef char[(condition)?1:-1] STATIC_ASSERT_ ## name
    

    如果 user<> 的声明可以更改,并且您想要两个实现,具体取决于 is_Data 的值,那么还有一个不使用SFINAE的解决方案:

    template<typename T, bool D=is_Data<T>::value> class User;
    
    template<typename T> class User<T, true> {
      static_assert(is_Data<T>::value, "T is not (a subclass of) Data<>"); // Optional
      /* Data implementation */
    };
    
    template<typename T> class User<T, false> {
      static_assert(!is_Data<T>::value, "T is (a subclass of) Data<>"); // Optional
      /* Non-data implementation */
    };
    

    静态断言仅检查用户是否意外地错误地指定了模板参数 D . 如果未明确指定 D ,则可以省略静态断言 .

相关问题