44

假设我有这些声明

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

并希望实现User<>forT = 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不用于偏特化。)

4

3 回答 3

27

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。我还在等待更好的答案。这可能最终证明没有其他解决方案是可能的

于 2012-10-12T13:09:09.543 回答
11

既然你说你还在等待更好的答案,这就是我的看法。它并不完美,但我认为它可以让你尽可能地使用 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,如上所述。旧版本不喜欢decltypein default 参数,但该技术本身仍然有效 - 用另一种表达约束的方式替换 default 参数使其适用于 2013 Update 4。
于 2015-06-22T22:46:26.643 回答
6

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

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

另请参阅:何时使用static_assert而不是 SFINAE?. 这static_assert是一个 c++11 构造,但是对于 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未明确指定,则可以省略静态断言。

于 2014-02-19T20:14:10.050 回答