简体   繁体   中英

Why does the compiler invoke a templated copy constructor when assigning?

Consider the code below:

#include <iostream>

template<class T>
struct X
{
    X() = default;

    template<class U>
    X(const X<U>&)
    {
        std::cout << __PRETTY_FUNCTION__ << std::endl;
    }

/*
    template<class U>
    X& operator=(const X<U>&)
    {
        std::cout << __PRETTY_FUNCTION__ << std::endl;
        return *this;
    }
*/

};

int main()
{
    X<int> a;
    X<double> b;

    b = a;
}

Live on Coliru

As you can see, the assignment operator is commented out. However, the line

b = a;

compiles fine. I thought it should not compile, as a and b have different types, and the operator=(const X&) that is generated by default by the compiler will be instantiated by the underlying type, so it won't assign an X<int> to an X<double> .

To my surprise, the code compiles, and it seems that the templated copy constructor is being invoked. Why is this? Is it because the compiler tries first to cast the a to a b then invoke the default-generated B::operator= ?

If T and U are different types, template<class U> X(const X<U>&) is not a copy constructor because it's argument is of different type. In other words, X<int> and X<double> are unrelated types, so this constructor is just a user-defined conversion between them.

Note that this code won't print anything:

X<int> a;
X<int> b { a };

Because in this case the implicitly-declared copy constructor of the form X<int>::X(const X<int>&) will be invoked.

The compiler is generating a call to X<double>(const X<int>&) to convert an X<int> into an X<double> . Then it's calling the generated assignment operator X<double>& X<double>::operator =(const X<double>&); to do the assignment.

If you wrote out all the steps explicitly it would be:

b.operator =(X<double>(a));

template<class U> X(const X<U>&) is a generalized user-defined conversion -- see Anton Savin's answer.

The rules for implicit type conversions are quite complex, so you should be wary of user-defined conversion functions ( More Effective C++ Item 5 ). This is even more the case with template functions.

The code

#include <iostream>

template<class T>
struct X
{
    X() = default;

    template<class U>
    X(const X<U>&)
    {
        std::cout << "generalized ctor: " << __PRETTY_FUNCTION__ << std::endl;
    }

/*
    template<class U>
    X& operator=(const X<U>&)
    {
        std::cout << "generalized assignment: " << __PRETTY_FUNCTION__ << std::endl;
        return *this;
    }
*/
};

int main()
{
    X<int> a;
    X<double> b;

    b = a;
}

returns

generalized ctor: X::X(const X&) [with U = int; T = double]

But for operator= not commented out, it returns

generalized assignment: X& X::operator=(const X&) [with U = int; T = double].

So you are correct that the implicitly generated operator=(const X&) will be instantiated by the underlying type and won't assign an X<int> to an X<double> . As Scott Meyers (see reference above) explains, your compiler is faced with a call to b.operator= of a X<double> , which takes a const X<double> , and finds that no such function exists. Therefore, your compiler then tries to find an acceptable sequence of implicit type conversions it can apply to make the call succeed, see rules in the cpp reference . Your compiler finds your generalized user-defined conversion and converts your X<int> to X<double> to have the right argument type for b.operator= .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM