Having following code, why the first assignment doesn't call template operator=
in Foo
, but second does? What is going one here? Is there compiler-generated one for the first assignment even if user defined template exists?
#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;
}
The compiler is still generating a non-templated operator=
which the first assignment binds to. In the second assignment, the templated operator=
is a better candidate (because it involves no cast), so that one is picked.
You can see that by adding the following in your code:
Foo& operator=(const Foo&) = delete;
Or forcing the proper template call:
pa.operator=<Foo>(b);
The standard says (emphasis mine):
12.8 Copying and moving class objects, §12.8/17, page 271:
A user-declared copy assignment operator X::operator= is a non-static non-template member function of class X with exactly one parameter of type X, X&, const X&, volatile X& or const volatile X&
§12.8/18, same page:
If the class definition does not explicitly declare a copy assignment operator, one is declared implicitly.
why the first assignment doesn't call template operator= in Foo, but second does? What is going one here?
Apart from the fact explained by William's answer on implicitly generated copy assignment operator function, overload resolution also has a play in this. Below are the candidates for the first assignment operator, after the template argument substitution, as seen by the compiler:
Foo& operator=(const Foo&); // implicitly generated
void operator=(const Foo&); // template generated
All things being equal, nontemplate functions are preferred over function templates. As per C++11 (draft N3337), 13.3.3/1 (emphasis mine)
Given these definitions, a viable function F1 is defined to be a better function than another viable function F2 if for all arguments i, ICSi(F1) is not a worse conversion sequence than ICSi(F2), and then
— for some argument j, ICSj(F1) is a better conversion sequence than ICSj(F2), or, if not that,
— the context is an initialization by user-defined conversion (see 8.5, 13.3.1.5, and 13.3.1.6) and the standard conversion sequence from the return type of F1 to the destination type (i.e., the type of the entity being initialized) is a better conversion sequence than the standard conversion sequence from the return type of F2 to the destination type. [ ... ] or, if not that,
— F1 is a non-template function and F2 is a function template specialization, or, if not that,
— F1 and F2 are function template specializations, and the function template for F1 is more specialized than the template for F2 according to the partial ordering rules described in 14.5.6.2.
This explains why the non-template overload is picked. You can verify the same with a small exercise:
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
As for the second assignment, it sees
Foo& operator=(const Foo&); // implicitly generated
void operator=(const UberFoo&); // template generated
Here the template generated function is a closer match than the implicitly generated one and thus it was chosen.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With