以下代码有效,并且按预期找到了重载:
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 类型 NoBuzz 到 foo
你仍然会实例化模板,因为两个 NoBuzz
重载不匹配。
本文末尾是一个更复杂但肯定更清晰的解决方案。
解释
template<class T>
void foo (T&&); // (A)
void foo (NoBuzz const&); // (B)
您的代码段的问题是您的模板 (A) 可以以比您的重载 (B) 更匹配的方式实例化。 p>
当编译器发现您正在尝试使用类型为 NoBuzz
的 rvalue 参数调用名为 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/