c++ - 过载分辨率和通用引用参数

标签 c++ c++11

以下代码有效,并且按预期找到了重载:

struct HasBuzz
{
    void buzz() const {}
};

struct NoBuzz {};

template <typename T>
void foo(T const& t)
{
    t.buzz();
}

void foo(NoBuzz const&){}

int main()
{
    foo(HasBuzz{});
    foo(NoBuzz{});
}

但是,如果我用“通用引用”版本替换第一个重载,那么它就不再有效了。找不到 NoBuzz 的正确重载。

struct HasBuzz
{
    void buzz() const {}
};

struct NoBuzz {};

template <typename T>
void foo(T&& t)
{
    t.buzz();
}

void foo(NoBuzz const&){}

int main()
{
    foo(HasBuzz{});
    foo(NoBuzz{}); // error: NoBuzz has no member function buzz
}

我该怎么做才能让它发挥作用?

最佳答案

简单的解决方案

添加一个可使用 NoBuzz 类型的右值调用的重载。

void foo(NoBuzz const&){ }; 
void foo(NoBuzz&&)     { }; // overload for rvalues


注意:根据您的实际用例,这可能还不够,因为如果您传递非常量 lvalue 类型 NoBuzzfoo 你仍然会实例化模板,因为两个 NoBuzz 重载不匹配。

本文末尾是一个更复杂但肯定更清晰的解决方案。


解释

 template<class T>
 void foo (T&&);            // (A)
 void foo (NoBuzz const&);  // (B)

您的代码段的问题是您的模板 (A) 可以以比您的重载 (B) 更匹配的方式实例化。 p>


当编译器发现您正在尝试使用类型为 NoBuzzrvalue 参数调用名为 foo 的函数时,它将查找所有名为 foo 的函数都接受一个适合 NoBuzz 的参数。

假设它以您的模板 (A) 开始,在这里它看到 T&& 可推导为任何引用类型(左值 , 和 rvalue), 因为我们传递的是 rvalue T = NoBuzz.

使用 T = NoBuzz 实例化的模板在语义上等同于:

void foo (NoBuzz&&); // (C), instantiated overload of template (A)


然后它将继续您的重载(B)。此重载接受一个 const 左值引用,它可以绑定(bind)到左值和右值;但是我们之前的模板实例化 (C) 只能绑定(bind)到 rvalues

因为 (C)(B) 更匹配,所以将右值绑定(bind)到 T&& 优于 U const&,选择了该重载,您将获得您在帖子中描述的行为。


高级解决方案

我们可以使用一种称为 SFINAE 的技术如果传递的类型未实现 .buzz (),则有条件地使其无法调用模板。

template <typename T>
auto foo(T&& t) -> decltype (t.buzz ())
{
  return t.buzz();
}

上面的解决方案使用了很多C++11的新特性,详细信息可以在这里找到:

关于c++ - 过载分辨率和通用引用参数,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/24043018/

相关文章:

c++ - constexpr 默认的默认构造函数

c++ - 虚函数实现缺少类型说明符

c++ - 如何在 ubuntu 中为 C++ 安装 mongodb 驱动程序?

C++类模板错误

c++ - 可能的内存泄漏?

c++ - 为什么我们需要 C++ 中的复制构造函数和赋值运算符

C++11/14 : How to remove a pointer-to-member from a type?

c++ - 堆栈容器适配器结构

c++ - 检测单个枚举常量的类型

c++ - 如何阻止 QXmlStreamWriter 转义引号?