【问题标题】:using SFINAE for template class specialisation使用 SFINAE 进行模板类专业化
【发布时间】:2012-10-03 06:08:07
【问题描述】:

假设我有这些声明

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

并希望为T = Data&lt;some_type&gt; 实现User&lt;&gt; 以及从Data&lt;some_type&gt; 派生的任何类,但也允许在别处定义的其他特化。

如果我还没有类模板 User&lt;&gt; 的声明,我可以简单地

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&lt;&gt; 仅使用一个模板参数声明。还有什么我可以做的吗?

(注意

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&lt;&gt; 的类型,所以也不允许

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

因为模板参数T 不用于偏特化。)

【问题讨论】:

  • SFINAE 可以用于选择模板专业,请参阅en.cppreference.com/w/cpp/types/enable_if
  • 可以!我学到了一些东西。
  • 澄清一下:您希望能够为任何Data&lt;&gt; 或子类实例化User&lt;&gt;,但对于任何其他类型User&lt;int&gt;应该编译失败吗?
  • @Walter 在这种情况下,为什么还要为部分专业化而烦恼呢?你不能在非专业版本中使用静态断言吗?
  • 请注意,对这种 SFINAE 使用 std::enable_if 有点迂回。使用typename = std::true_type 作为默认参数,您可以编写与&lt;T, typename is_foo&lt;T&gt;::type&gt; 匹配的部分规范,假设is_foo 是例如标准意义上的 UnaryTypeTrait(&lt;type_traits&gt; 中的任何特征都是这样工作的)。

标签: c++ templates c++11 sfinae


【解决方案1】:

IF原来的User&lt;&gt;声明可以改编成

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

【讨论】:

  • 该解决方案已在发布的链接中正确演示,但在此答案中未正确传输/改编 - 它应该是如下的部分专业化:template&lt;typename T&gt; class User&lt;T, typename std::enable_if&lt;is_Data&lt;T&gt;::value&gt;::type&gt; { ... }; ... 将发布一个“编辑”。
  • 我认为 this answer 可以在不修改原始定义的情况下适应 sfinae
  • @Predelnik 有趣的想法。我试过了,但无法让它工作。情况类似于问题中最后一个失败的示例(当编译器警告我 partial specialization contains a template parameter that cannot be deduced; this partial specialization will never be used),但我没有警告,但它也没有按要求获得专业化。如果你能让它工作,那就太好了。
  • @Walter 这很奇怪,code like this 适用于 gcc 5+ 版本,但 clang/gcc 4.9/msvc 拒绝它,因为专业化实际上并没有专门化任何东西。我还没有想出如何模拟现在可以使用的虚假专业化,但它导致了一个非常奇怪的结论,即只有当你也以某种方式进行专业化时,你才能像这样进行学习。这是one possible solution with weird indirection
【解决方案2】:

既然您说您仍在等待更好的答案,那么这就是我的看法。它并不完美,但我认为它可以让你尽可能地使用 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&lt;enable_if_data&lt;T&gt;&gt; 等价于User&lt;T&gt; (模替换到该默认参数,该默认参数是单独处理的,如上面第一个引号所述),因此是一种无效形式的部分特化。不幸的是,匹配DD 之类的东西通常需要T 形式的部分特化参数——它没有其他形式可以具有并且仍然匹配每种情况。所以,恐怕我们可以最终说这部分在给定的约束下是无法解决的。 (有Core issue 1980,它暗示了一些关于模板别名使用的未来可能的规则,但我怀疑它们会让我们的案例有效。)

只要派生自 Data&lt;T&gt; 的类本身是模板特化,使用上述技术进一步约束它们就可以了,所以希望这对您有所帮助。


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

  • 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&lt;T&gt; 的错误版本,因此可以作为临时解决方案。
  • MSVC:Visual C++ 2015,带有/W4,按上述方式工作。旧版本不喜欢默认参数中的 decltype,但该技术本身仍然有效 - 用另一种表达约束的方式替换默认参数使其适用于 2013 Update 4。

【讨论】:

    【解决方案3】:

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

    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 构造,但是对于 c++11 之前的编译器有很多可用的解决方法,例如:

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

    如果user&lt;&gt;的声明可以改变,并且你想要根据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,则可以省略静态断言。

    【讨论】:

    • 这实际上并没有解决我遇到的问题。我仍然希望允许Data&lt;T&gt; 的其他专业化(将编辑问题以提及)。
    猜你喜欢
    • 1970-01-01
    • 2015-08-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多