重载解析和通用参考参数
Overload resolution and universal reference parameters
下面的代码可以正常工作,并且发现了预期的重载:
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
注意:根据您的实际用例,这可能还不够,因为如果您传递一个非const 左值类型NoBuzz给foo
,您仍然会实例化模板,因为两个NoBuzz
重载不匹配。
在这篇文章的最后是一个更复杂,但肯定更干净的解决方案。
template<class T>
void foo (T&&); // (A)
void foo (NoBuzz const&); // (B)
你的代码片段的问题是你的模板(A)可以用这样一种方式实例化,它比你的重载(B)更匹配。
当编译器看到您正在尝试调用名为foo的函数,其参数是NoBuzz
类型的右值时,它将查找所有名为foo的函数,其参数为NoBuzz
。
T&&
可以演绎为任何引用类型(lvalue和rvalue),因为我们传递的是rvalue T = NoBuzz
。对于T = NoBuzz
,实例化的模板在语义上相当于:
void foo (NoBuzz&&); // (C), instantiated overload of template (A)
它将继续你的过载(B)。该重载接受一个const左值引用,它可以绑定到左值和右值;但是我们之前的模板实例(C)只能绑定到右值。
因为(C)是比(B)更好的匹配,将右值绑定到T&&
而不是U const&
,选择了重载,您得到了您在帖子中描述的行为。
先进解决方案
我们可以使用一种叫做SFINAE的技术,如果传递的类型不实现.buzz ()
,则有条件地使模板无法调用。
template <typename T>
auto foo(T&& t) -> decltype (t.buzz ())
{
return t.buzz();
}
上面的解决方案使用了很多 c++ 11的新特性,详细信息在这里:
- wikipedia.org - c++ -
auto
- wikipedia.org - decltype
- 可能有尾随返回类型的条件重载吗?
refp的回答解释了您所看到的行为,并提供了一个可能的解决方案。另一个选择是确保foo
函数模板不进入重载解析的候选集合,除非T
有一个名为buzz()
的成员函数。
template <typename T>
auto foo(T&& t)
-> decltype((void)(t.buzz()), void())
{
t.buzz();
}
在做了这个改变之后,当你传递一个NoBuzz
的实例时,foo(NoBuzz const&)
过载将被选中。现场演示
关于末尾返回类型的decltype
表达式中发生的事情的详细解释可以在这里找到。我在这里做的唯一不同的事情是使用三个子表达式,中间一个是void()
,以防止用户定义的operator,
被选中,我将第一个表达式的结果强制转换为void
;在这两种情况下,目的和结果是相同的。
- Lambda闭包左值可以作为右值参考参数传递
- 遍历地图并将该对用作参考参数 C++11
- 什么是常量参考参数以及如何使用它?
- 如何将rvalue作为参考参数传递给函数
- 移动 l 值参考参数是否是一种不好的做法?
- C 11:为什么RVALUE参考参数隐式转换为LVALUE
- 为什么可以将其他类型变量用作C 中常量参考参数参数的参数
- 如何处理可能指向内部数据的参考参数
- C 功能具有参考参数错误的迭代器错误.寻求解释
- 用emscripten中的参考参数绑定函数
- 一种体面的方式来转换const列表参考参数,然后传递到另一个函数
- 参考参数和递归
- 分配运算符过载:返回void与返回参考参数
- C ,构造器中允许的参考参数默认值
- Qt信号和插槽:是复制的参考参数
- C 接收const lvalue和rvalue参考参数,而无需过载
- std ::移动返回和输入参考参数
- 参考参数返回副本
- 如何使用数组和参考参数调用void函数
- 按值传递参考参数