为什么我的T&和T&&复制构造函数含糊不清?

时间:2013-06-08 11:25:52

标签: c++ templates c++11

#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&&’

1 个答案:

答案 0 :(得分:10)

所以这是发生的事情(或者更确切地说,应该发生):为了解决这个构造函数调用

Myclass b(a);

编译器必须执行重载解析,并首先确定哪些构造函数是可行的候选者。

首先要注意的是两个构造函数都是可行的:像T&&这样的形式而不是总是会解析为rvalue引用(只有当你传递的是rvalue时才是这种情况) 。这就是Scott Meyers所说的"universal references"(注意,这个术语不是标准的)。

当编译器尝试执行类型推导以查看第二个构造函数是否可行时,在这种情况下类型T将被推断为Myclass& - 因为你传递的是什么({{1 }})是一个左值;并且由于引用了折叠规则,a会给出Myclass& &&,因此您最终会得到第一个构造函数所具有的相同签名。

呼叫是否含糊不清?正如Marc Glisse in the comments to the questionJonathan Wakely in the comments to this answer所指出的那样,它不应该(正如此答案的原始版本所声称的那样 - mea culpa)。

原因是标准中的特殊规则指定接受左值引用的重载比接受右值引用的重载更专用。根据C ++ 11标准的第14.8.2.4/9段:

  

如果对于给定类型,推导在两个方向上都成功(即,在转换之后类型是相同的   以上)和P和A都是参考类型(在被所述类型替换之前)   以上):

     

- 如果参数模板中的类型是左值引用,则参数类型   模板不是,参数类型被认为比其他类型更专业;否则,[...]

这意味着编译器有一个错误(Marc Glisse在对问题的评论中提供了the link to the bug report)。

要解决此错误并确保只有在传递rvalues时,GCC才会选择接受Myclass&的构造函数模板,您可以这样重写:

T&&

我添加了SFINAE约束,这使得编译器在传递左值时从重载集中丢弃此构造函数。

事实上,当左值传递时,对于某些 #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++;} (您传递的表达式类型T),X&将被推断为X。案例),Myclass将解析为T&&;另一方面,当rvalue被传递时,X&将从某些T推断为X(您传递的表达式类型X案例),Myclass将解析为T&&

由于SFINAE约束检查X&&是否被推断为引用类型并且否则会创建替换失败,因此只有当参数是rvalue表达式时才能保证构造函数被考虑。

总而言之:

T

这是live example