c – 为什么我的T&和T \u0026\u0026复制构造函数不明确?

#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时才是这种情况).这就是Scott Meyers所说的“universal references”(注意,这个术语不是标准的).

当编译器尝试执行类型推导以查看第二个构造函数是否可行时,在这种情况下类型T将推断为Myclass& – 因为你传递的是(a)是左值;并且由于参考折叠规则,Myclass& &安培;&安培;给出了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段:

If, for a given type, deduction succeeds in both directions (i.e., the types are identical after the transformations
above) and both P and A were reference types (before being replaced with the type referred to
above):

if the type from the argument template was an lvalue reference and the type from the parameter
template was not, the argument type is considered to be more specialized than the other
; otherwise, […]

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

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

    #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约束,这使得编译器在传递左值时从重载集中丢弃此构造函数.

事实上,当一个左值通过时,T将被推断为X&对于某些X(您传递的表达式的类型,在您的情况下为Myclass),以及T&&将解析为X&amp ;;另一方面,当一个rvalue被传递时,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);
}

这是live example.

转载注明原文:c – 为什么我的T&和T \u0026\u0026复制构造函数不明确? - 代码日志