以下代码可以正常工作,并且可以按预期找到重载:
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
}
我该怎么做才能让它发挥作用?
答案 0 :(得分:9)
简单解决方案
添加一个可以使用NoBuzz类型的 rvalue 调用的重载。
void foo(NoBuzz const&){ };
void foo(NoBuzz&&) { }; // overload for rvalues
注意:根据您的实际使用情况,这可能还不够,因为如果您传递非const 左值类型 NoBuzz 到foo
您仍然会实例化模板,因为两个NoBuzz
重载并不匹配。
在这篇文章的最后是一个更复杂但更清晰的解决方案。
<强>解释强>
template<class T>
void foo (T&&); // (A)
void foo (NoBuzz const&); // (B)
您的代码段的问题在于,您的模板(A)可以通过这样的方式进行实例化:它比您的重载(B)更好地匹配
当编译器发现您尝试使用类型为NoBuzz
的 rvalue 参数调用名为 foo 的函数时,它将查找名为的所有函数 foo 在NoBuzz
适合的情况下选择一个参数。
让我们说你的模板(A)开始了,在这里它看到T&&
可以被任何引用类型(两个 lvalue 和 rvalue ),因为我们传递的是 rvalue T = NoBuzz
。
使用T = NoBuzz
实例化模板在语义上等同于:
void foo (NoBuzz&&); // (C), instantiated overload of template (A)
然后它会继续你的重载(B)。此重载接受 const左值引用,它可以绑定到左值和右值;但是我们之前的模板实例化(C)只能绑定到 rvalues 。
由于(C)是比(B)更好的匹配,因此T&&
的绑定rvalues优先于U const&
,该重载是选中后,您将获得您在帖子中描述的行为。
高级解决方案
如果传递的类型没有实现.buzz ()
,我们可以使用名为SFINAE的技术来有条件地调用模板。
template <typename T>
auto foo(T&& t) -> decltype (t.buzz ())
{
return t.buzz();
}
上述解决方案使用 C ++ 11 的许多新功能,详细信息可在此处获取:
答案 1 :(得分:2)
refp的answer解释了您所看到的行为,并提供了一种可能的解决方案。另一个选项是确保foo
函数模板不会使其成为重载决策的候选集,除非T
具有名为buzz()
的成员函数。
template <typename T>
auto foo(T&& t)
-> decltype((void)(t.buzz()), void())
{
t.buzz();
}
进行此更改后,当您向其传递foo(NoBuzz const&)
的实例时,将选择NoBuzz
重载。 Live demo
可以找到尾随返回类型中decltype
表达式中发生的内容的详细说明here。我在这里做的唯一不同的是,不是使用三个子表达式,中间的一个是void()
,以防止用户定义的operator,
被选中,我已经转换了第一个表达式的结果到void
;在两种情况下,意图和结果都是相同的。