【问题标题】:How to specialize a template class method for a specific type?如何为特定类型专门化模板类方法?
【发布时间】:2019-07-19 03:17:02
【问题描述】:

我有这样的代码:

class Bar {
 public:
  void print() {
    std::cout << "bar\n";
  }
};

template<typename T>
class Foo {
 public:
  template <typename std::enable_if<std::is_base_of<T,Bar>::value,T>::type>
  void print() {
    t.print();
  }

 template <typename>
  void print() {
    std::cout << t << std::endl;
  }
 private:
  T t;
};

int main() {
//  Foo<int> foo1;
  Foo<Bar> foo2;
  foo2.print();
}

这段代码的目的是:如果T tBar或者Bar的子类,那么foo.print()推导为void print() {t.print();},否则推导为void print() {std::cout &lt;&lt; t &lt;&lt; std::endl;},但事情并没有不像我预期的那样工作。编译器错误:

"一个非类型模板参数不能有类型'typename std::enable_if::value, Bar>::type' (又名 '酒吧')",

这段代码有什么问题?

【问题讨论】:

  • template &lt;typename 之后,需要一个标识符。与template &lt;typename T&gt; 一样。您对print 的定义不是有效的C++。
  • @IgorTandetnik,我将...::type&gt; 更改为...::type* = nullprt&gt; 并且有效,但我不知道为什么要这样写。
  • 如果没有 =,您无法为 typename 模板参数命名默认值。

标签: c++ c++11 compiler-errors sfinae template-specialization


【解决方案1】:
  1. 您应该同时将print() 重载为函数模板(以使SFINAE 工作),否则始终首选非模板函数。

  2. 你应该让print()拥有自己的模板类型参数;不应该直接对类模板参数T进行类型检查,函数模板重载解析和SFINAE是对函数模板本身进行的,类模板不参与。

  3. 可以将std::enable_if的部分移到返回类型中。

  4. 如果您希望类型为BarBar 的派生类,则应将指定的顺序更改为std::is_base_of(即std::is_base_of&lt;Bar, X&gt;,而不是std::is_base_of&lt;X, Bar&gt;)。

例如

template <typename X = T>
typename std::enable_if<std::is_base_of<Bar, X>::value>::type print() {
  t.print();
}

template <typename X = T>
typename std::enable_if<!std::is_base_of<Bar, X>::value>::type print() {
  std::cout << t << std::endl;
}

LIVE

【讨论】:

  • 如果我想让我的函数返回无效怎么办?或者在其他情况下返回类型是固定的?
  • @reavenisadesk 如果不指定,返回类型为void(作为我的示例代码)。如果要其他类型,可以显式指定给std::enable_if,反正类型是固定的。
  • @reavenisadesk std::enable_if应该依赖函数模板print()自己的模板参数;不是类模板Foo之一。
  • @reavenisadesk 如果enable_if 不依赖于print 函数的模板参数,编译器会立即展开它,这至少会在一种情况下导致错误。使用print 函数的模板参数会强制编译器等待展开返回类型,直到它尝试展开整个print 模板。而且由于替换失败不是错误,并且存在正确扩展且没有错误的版本,因此编译器不会将其标记为错误。
  • @reavenisadesk 函数模板重载解析和 SFINAE 在函数模板本身上执行;类模板不涉及。
【解决方案2】:

由于您实际上对某个类型是否具有成员函数 print 或已定义 operator&lt;&lt; 感兴趣,因此您也应该以这种方式对其进行约束。

随着即将推出的C++20 标准,我们将获得concepts & constraints。考虑到这一点,我们可以执行以下操作:

namespace traits
{
template<typename T>
concept has_print_v = requires(T&& t) { t.print(); };

template<typename T>
concept has_ostream_op_v = requires(T&& t, std::ostream& os) { os << t; };
} // end of namespace traits

并使用这样的概念:

template<typename T>
class Foo
{
public:
    void print()
    {
        if constexpr (traits::has_print_v<T>) { t.print(); }
        else if constexpr (traits::has_ostream_op_v<T>) { std::cout << t << "\n"; }
    }
private:
    T t;  
};

LIVE DEMO

【讨论】:

  • 现在有编译器支持 C++20 吗?
【解决方案3】:

您应该在调用print() 时添加模板参数,因为它本身就是一个模板方法。无论如何,您的设计过于复杂!

使用 C++17 变得非常简单,您只需要 一个 Foo&lt;T&gt;::print() 方法。

void print() {
  if constexpr(std::is_base_of_v<T,Bar>) // ignores `Foo<int>`
    t.print();
}

Demo.

【讨论】:

  • if constexpr(std::is_base_of_v&lt;T,Bar&gt;) 真的会生成比较命令吗?(在效率方面询问)
  • @reavenisadesk,它非常高效,因为if 条件是在编译期间评估的。这与您使用 enable_if 类型的 type_traits 所做的一样好,但非常简洁。简而言之,if constexpr 是您的查询类型的笔记本示例。 :-)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2013-04-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-06-15
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多