使用SFINAE进行模板类专业化

Wal*_*ter 37 c++ templates sfinae c++11

假设我有这些声明

template<typename T> class User;
template<typename T> class Data;
Run Code Online (Sandbox Code Playgroud)

并且想要实现User<>for T = Data<some_type> 和派生的任何类,Data<some_type>但也允许在别处定义的其他特化.

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

template<typename T,
         typename A= typename std::enable_if<is_Data<T>::value>::type>
class User { /*...*/ };
Run Code Online (Sandbox Code Playgroud)

哪里

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

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

(注意

template<typename T,
         typename A= typename std::enable_if<is_Data<T>::value>::type>
class User<T> { /*...*/ };
Run Code Online (Sandbox Code Playgroud)

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

template<typename T> class User<Data<T>> { /*...*/ };
Run Code Online (Sandbox Code Playgroud)

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

template<typename T>
class User<typename std::enable_if<is_Data<T>::value,T>::type>
{ /*...*/ };
Run Code Online (Sandbox Code Playgroud)

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

Wal*_*ter 22

如果原始声明User<>可以适应

template<typename, typename=std::true_type> class User;
Run Code Online (Sandbox Code Playgroud)

然后我们可以找到一个解决方案(按照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 >
{ /* ... */ };
Run Code Online (Sandbox Code Playgroud)

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


bog*_*dan 9

既然你说你还在等待更好的答案,这就是我的看法.它并不完美,但我认为它会尽可能地使用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>(); // error, int does not have a nested type foo
Run Code Online (Sandbox Code Playgroud)

- 末端的例子]

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

#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();
}
Run Code Online (Sandbox Code Playgroud)

运行它打印:

primary
partial specialization for Data
partial specialization for Data
partial specialization for Data
primary
partial specialization for Other
Run Code Online (Sandbox Code Playgroud)

正如你所看到的,有一个皱纹:DD由于我们宣布它的方式,因此没有选择部分特化,也不能选择.那么,我们为什么不说

template<typename T> struct User<enable_if_data<T>> 
Run Code Online (Sandbox Code Playgroud)

并允许它匹配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- 它没有其他形式,并且仍然匹配每个案例.所以,我担心我们可以最终确定这部分在给定的约束条件下无法解决.(1980年核心问题,暗示了一些关于模板别名使用的未来规则,但我怀疑它们会使我们的案例有效.)

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


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

  • Clang 3.3 - 3.6.0,with -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.


bcm*_*inc 5

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

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

另请参阅:何时使用static_assert而不是SFINAE?.这static_assert是一个c ++ 11构造,但是有很多可用于pre-c ++ 11编译器的解决方法,例如:

#define STATIC_ASSERT(consdition,name) \
  typedef char[(condition)?1:-1] STATIC_ASSERT_ ## name
Run Code Online (Sandbox Code Playgroud)

如果声明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 */
};
Run Code Online (Sandbox Code Playgroud)

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

  • 这实际上并没有解决我遇到的问题。我仍然希望允许“Data&lt;T&gt;”的其他专业化(将编辑问题以提及这一点)。 (2认同)