[英]Why are my T& and T&& copy constructors ambiguous?
#include <iostream>
using namespace std;
class Myclass{
private:
int i;
public:
template<typename U>Myclass(U& lvalue):i(lvalue){cout<<i <<" template light reference" <<endl;i++;}
//Myclass(Myclass &lvalue):i(lvalue){cout<<i <<" light reference" <<endl;i++;}
template<typename U>Myclass(U&& rvalue):i(rvalue){cout<<i <<" template right reference" <<endl;i++;}
};
int main(int argc,char*argv[])
{
Myclass a(0);
Myclass b(a);
Myclass c(2);
return 0;
}
错误信息:
rightvalue.cpp: In function ‘int main(int, char**)’:
rightvalue.cpp:15:12: error: call of overloaded ‘Myclass(Myclass&)’ is ambiguous
rightvalue.cpp:15:12: note: candidates are:
rightvalue.cpp:10:23: note: Myclass::Myclass(U&&) [with U = Myclass&]
rightvalue.cpp:8:23: note: Myclass::Myclass(U&) [with U = Myclass]
rightvalue.cpp:4:7: note: constexpr Myclass::Myclass(const Myclass&)
rightvalue.cpp:4:7: note: constexpr Myclass::Myclass(Myclass&&) <near match>
rightvalue.cpp:4:7: note: no known conversion for argument 1 from ‘Myclass’ to ‘Myclass&&’
所以这是发生了什么(或者更确切地说, 应该发生):为了解决这个构造函数调用
Myclass b(a);
编译器必须执行重载决策,并首先确定哪些构造函数是可行的候选者。
首先要注意的是两个构造函数都是可行的:像T&&
这样的形式并不总是解析为rvalue引用(只有当你传递的是rvalue时才是这种情况)。 这就是Scott Meyers所说的“通用参考” (注意,这个术语不是标准的)。
当编译器尝试执行类型推导以查看第二个构造函数是否可行时,在这种情况下类型T
将推断为Myclass&
- 因为您传递的是( a
)是左值; 并且由于参考折叠规则, Myclass& &&
给出了Myclass&
,因此您最终得到了第一个构造函数具有的相同签名。
呼叫是否含糊不清? 正如Marc Glisse在对该问题的评论中所指出的 ,以及Jonathan Wakely在对这个答案的评论中所指出的 , 不 ,它不应该(正如这个答案的原始版本所声称的那样 - mea culpa)。
原因是标准中的特殊规则指定接受左值引用的重载比接受右值引用的重载更专用 。 根据C ++ 11标准的第14.8.2.4/9段:
如果对于给定类型,推导在两个方向上都成功(即,在上面的转换之后类型是相同的)并且P和A都是引用类型(在被替换为上面提到的类型之前):
- 如果参数模板中的类型是左值引用而参数模板中的类型不是,则参数类型被认为比其他类型更专业 ; 除此以外, [...]
这意味着编译器有一个错误( 错误报告的链接由Marc Glisse在对问题的评论中提供)。
要解决此错误并确保只有在传递rvalues时,GCC才会选择接受T&&
构造函数模板,您可以通过以下方式重写它:
#include <type_traits>
template<typename U,
typename std::enable_if<
!std::is_reference<U>::value
>::type* = nullptr>
Myclass(U&& rvalue):i(rvalue)
{cout<<i <<" template right reference" <<endl;i++;}
我在其中添加了SFINAE约束,这使得编译器在传递左值时从重载集中丢弃此构造函数。
实际上,当一个左值被传递时,对于某些X
(你传递的表达式的类型,在你的情况下是Myclass
), T
将被推导为X&
,并且T&&
将解析为X&
; 当右值被传递,而另一方面, T
会被推断为X
来自一些X
(你传递表达的类型, Myclass
你的情况),并T&&
会分解成X&&
。
由于SFINAE约束检查T
是否被推断为引用类型并且否则会创建替换失败,因此只有当参数是rvalue表达式时才能保证构造函数被考虑。
总而言之:
#include <iostream>
#include <type_traits>
class Myclass
{
int i;
public:
template<typename U>
Myclass(U& lvalue):i(lvalue)
{
std::cout << i <<" template light reference" << std::endl;
i++;
}
template<typename U,
typename std::enable_if<
!std::is_reference<U>::value
>::type* = nullptr>
Myclass(U&& rvalue):i(rvalue)
{
std::cout << i <<" template right reference" << std::endl;
i++;
}
};
int main(int argc,char*argv[])
{
Myclass a(0);
int x = 42;
Myclass b(x);
Myclass c(2);
}
这是一个实例 。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.