假设我有这些声明
template<typename T> class User;
template<typename T> class Data;
并希望为User<>
以及从T = Data<some_type>
派生的任何类实施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
未在部分特化中使用。)
答案 0 :(得分:20)
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
的原始定义。我还在等待更好的答案。这可能是最终证明没有其他解决方案可行的。
答案 1 :(得分: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
-end example]
这是实现这一想法的完整示例:
#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
?这实际上适用于海湾合作委员会,但由于[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>
派生的类本身就是模板特化,使用上述技术进一步限制它们就可以了,所以希望这对你有用。
编译器支持(这是我测试的,其他版本也可以工作):
-Wall -Wextra -std=c++11 -pedantic
- 按上述方式工作。enable_if_data<T>
接受不正确的版本,因此这可以是一个临时解决方案。/W4
,如上所述。较旧的版本不喜欢默认参数中的decltype
,但该技术本身仍然有效 - 用另一种表达约束的方式替换默认参数使其适用于2013 Update 4。答案 2 :(得分:5)
由于您只想在单个条件为真时实现它,最简单的解决方案是使用静态断言。它不需要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
,则可以省略静态断言。