既然你说你还在等待更好的答案,这就是我的看法。它并不完美,但我认为它可以让你尽可能地使用 SFINAE 和部分专业化。(我猜 Concepts 将提供一个完整而优雅的解决方案,但我们将不得不等待更长的时间。)
该解决方案依赖于最近才在 C++14 最终版本之后的标准工作草案中指定的别名模板功能,但已被实现支持一段时间。N4527 [14.5.7p3] 草案中的相关措辞是:
但是,如果模板 ID 是依赖的,则后续模板参数替换仍适用于模板 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
—结束示例]
这是实现此想法的完整示例:
#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
,而且它不能被选择,因为我们声明它的方式。那么,我们为什么不直接说
template<typename T> struct User<enable_if_data<T>>
并允许它匹配DD
?这实际上在 GCC 中有效,但由于 [14.5.5p8.3, 8.4] 被 Clang 和 MSVC 正确拒绝([p8.3] 将来可能会消失,因为它是多余的 - CWG 2033):
- 特化的参数列表不应与主模板的隐式参数列表相同。
- 专业化应比主要模板 (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
in default 参数,但该技术本身仍然有效 - 用另一种表达约束的方式替换 default 参数使其适用于 2013 Update 4。