C++ 使用模板赋值运算符

C++ 使用模板赋值运算符,c++,templates,C++,Templates,有了下面的代码,为什么第一个赋值不调用Foo中的templateoperator=,而第二个赋值调用?这是什么?即使存在用户定义的模板,编译器是否为第一个赋值生成了一个 #include <iostream> using namespace std; struct UberFoo { }; struct Foo : public UberFoo { template<typename T> void operator=(const T& v) {

有了下面的代码,为什么第一个赋值不调用
Foo
中的template
operator=
,而第二个赋值调用?这是什么?即使存在用户定义的模板,编译器是否为第一个赋值生成了一个

#include <iostream>

using namespace std;

struct UberFoo { };

struct Foo : public UberFoo 
{
    template<typename T> void operator=(const T& v) { cout << "const T&" << endl; Set(v); }
    template<typename T> void operator=(T& v) { cout << "T&" << endl; return Set(v); }

    virtual void Set(const Foo&) { cout << "Foo::Set(const Foo&)" << endl; }
    virtual void Set(const UberFoo&) { cout << "Foo::Set(const UberFoo&)" << endl; }
};

struct Bar : public Foo 
{
    virtual void Set(const Foo&) { cout << "Bar::Set(const Foo&)" << endl; }
    virtual void Set(const UberFoo&) { cout << "Bar::Set(const UberFoo&)" << endl; }
};

int main()
{
    Bar a, b;

    Foo & pa = a;
    const Foo& rb = b;
    const UberFoo & urb = b;

    cout << "First" << endl;

    pa = rb;

    cout << endl << "Second" << endl;

    pa = urb;

    return 0;
}
#包括
使用名称空间std;
结构UberFoo{};
结构Foo:公共UberFoo
{

template void operator=(const T&v){cout编译器仍在生成第一个赋值绑定到的非模板化
运算符=
。在第二个赋值中,模板化
运算符=
是更好的候选对象(因为它不涉及强制转换),因此会选择一个

通过在代码中添加以下内容,您可以看到:

Foo& operator=(const Foo&) = delete;
或强制执行正确的模板调用:

pa.operator=<Foo>(b);
pa.operator=(b);
说(强调我的):

12.8复制和移动类对象,§12.8/17,第271页:

用户声明的复制赋值运算符X::operator=是类X的非静态非模板成员函数,只有一个类型为X、X&、常量X&、volatile X&或常量volatile X的参数&

§12.8/18,同一页:

如果类定义没有显式声明复制赋值运算符,则隐式声明一个

为什么第一个赋值在Foo中不调用template operator=而第二个调用?这里的一个是什么

除了William关于隐式生成的复制赋值运算符函数的回答所解释的事实外,重载解析在这方面也起到了作用。以下是在模板参数替换之后,编译器看到的第一个赋值运算符的候选对象:

Foo& operator=(const Foo&);        // implicitly generated
void operator=(const Foo&);        // template generated
在所有条件相同的情况下,非模板函数优先于函数模板

根据这些定义,一个可行函数F1被定义为比另一个可行函数F2更好的函数,如果对于所有参数i,ICSi(F1)不是比ICSi(F2)更差的转换序列,则

-对于某些参数j,ICSj(F1)是比ICSj(F2)更好的转换序列,或者,如果不是这样

-上下文是通过用户定义转换进行的初始化(见8.5、13.3.1.5和13.3.1.6)和 从F1返回类型到目标类型的标准转换顺序(即 实体(正在初始化)是一个比标准转换序列更好的转换序列 将F2的返回类型设置为目标类型。[…],或者,如果不是该类型

-F1是非模板函数,F2是函数模板专用化,如果不是这样

-F1和F2是函数模板专用化,F1的函数模板更专用 根据14.5.6.2中所述的偏序规则,F2的模板

这解释了选择非模板重载的原因。您可以通过一个小练习来验证这一点:

void print(int, int) { std::cout << "int"; }
template <typename T> void print(T, T) { std::cout << "T"; }
print(1, 2); // prints int
print<>(1, 2); // prints T

这里,模板生成的函数比隐式生成的函数更接近匹配,因此选择了它。

我知道赋值运算符应该将Ref返回到self(以使链接成为可能)但这不是重点是的,除非显式删除,否则总是定义复制赋值运算符。如果不定义复制赋值运算符,编译器将定义。生成的签名是什么样子的?当
t
被推断为
Foo
时,签名是相同的:
void运算符=(const Foo&)
所以这和编译器生成的一样好。为什么
T
不被推断为
Foo
?@relaxx:它可能“一样好”作为复制赋值运算符,但根据此答案中引用的规则,它不是复制赋值运算符。
T
可以推断为
Foo
,但没有必要这样做,因为已经有一个匹配的非模板函数要调用,并且具有优先权。@relaxx正如LRIO所说,签名是相同的,但是该函数是模板化的。在重载解析过程中,非模板比模板更匹配,因此删除的运算符被选为最佳候选运算符。在您输入standard中的quote之前,我已经写了注释(我应该按照您的建议等待:)。我不知道模板
运算符=
不是实际的赋值运算符。所以现在很清楚为什么它会这样工作。谢谢您的回答!
Foo& operator=(const Foo&);      // implicitly generated
void operator=(const UberFoo&);  // template generated