以下代码工作,并按预期找到重载:
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{});
}
Run Code Online (Sandbox Code Playgroud)
但是,如果我用"通用引用"版本替换第一个重载,那么它就不再起作用了.找不到正确的过载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
}
Run Code Online (Sandbox Code Playgroud)
我能做些什么才能让它发挥作用?
简单解决方案
添加一个可以使用NoBuzz类型的右值调用的重载.
void foo(NoBuzz const&){ };
void foo(NoBuzz&&) { }; // overload for rvalues
Run Code Online (Sandbox Code Playgroud)
注意:根据您的实际用例,这可能还不够,因为如果您传递非const 左值类型NoBuzz,foo您仍然会实例化模板,因为两个NoBuzz重载不匹配.
在这篇文章的最后是一个更复杂,但肯定更清洁的解决方案.
说明
template<class T>
void foo (T&&); // (A)
void foo (NoBuzz const&); // (B)
Run Code Online (Sandbox Code Playgroud)
你的代码片段的问题在于你的模板(A)可以以比你的重载(B)更好的匹配方式进行实例化.
当编译器发现你试图用一个类型为rvalue的参数调用一个名为foo的函数时,它会查找名为foo的所有函数,这些函数采用一个适合的参数.NoBuzzNoBuzz
让我们说它从你的模板(A)开始,在这里它看到T&&任何引用类型(左值和右值)都可以推断,因为我们传递一个右值 T = NoBuzz.
使用T = NoBuzz实例化模板在语义上等同于:
void foo (NoBuzz&&); // (C), instantiated overload of template (A)
Run Code Online (Sandbox Code Playgroud)
然后它将继续你的过载(B).这个重载接受一个const左值引用,它可以绑定到左值和右值; 但是我们之前的模板实例化(C)只能绑定到rvalues.
由于(C)是比(B)更好的匹配,T&&因此优先U const&选择绑定rvalues ,选择重载并获得您在帖子中描述的行为.
高级解决方案
如果传递的类型没有实现,我们可以使用一种名为SFINAE的技术来有条件地调用模板.buzz ().
template <typename T>
auto foo(T&& t) -> decltype (t.buzz ())
{
return t.buzz();
}
Run Code Online (Sandbox Code Playgroud)
上述解决方案使用了C++ 11的许多新功能,详细信息可在此处获得: