'Lvalue-to-rvalue conversion for class types: is there copying involved?

(I asked this question before but didn't give a viable example so I deleted previous one. I hope on this one I got the example right.)

Case:

#include <iostream>

struct S
{
    S() = default;

    S(const S &)
    {
        std::cout << "Copying" << std::endl;
    }

    S& operator=(const S &)
    {
        std::cout << "Copy assignment" << std::endl;
        return *this;
    }
};

int main()
{
    S s{};
    S s2{};
    S &ref = s;
    ref = s2;
}

As I understand, ref = s2; includes l2r conversion as it is a 'builtin direct assignment' that per cppreference expects an rvalue as its right argument.

I've read some of the SO questions regarding lvalue-to-rvalue conversion, but I'm still not sure if it involves copying of objects, and if it does, what kind of copying that is.

Let's say we're talking about class types.

From [conv.lval]/2:

Otherwise, if T has a class type, the conversion copy-initializes a temporary of type T from the glvalue and the result of the conversion is a prvalue for the temporary.

So, there is copy-initialization involved as a part of lvalue-to-rvalue conversion.

So taking an example of ref = s2;, with user-defined copy constructor that e.g. prints 'Copying', will that 'Copying' be printed during the execution of the aforementioned statement?

Well, obviously it won't. But that means I'm misunderstanding something here.

Is the copy-initialization during lvalue-to-rvalue conversion something like plain memcpy and not a copy-initialization in its full sense?

How all of this works? :)



Solution 1:[1]

As I understand, ref = s2; includes l2r conversion as it is a 'builtin direct assignment' that per cppreference expects and rvalue as its right argument.

Your mistake is in interpreting the assignment operator here is a built-in. It is not. The only types that have a built-in assignment operator are the fundamental types (pointers, char, int, etc.) What you have is a class type, which has an overloaded assignment operator (whether user-provided or implicitly generated by the compiler).

ref = s2; simply invokes S::operator=(S const&). It behaves as if you just typed ref.operator=(s2). There is no lvalue-to-rvalue conversion in that function's implementation, so no other copy is made. All that happens is that the line gets printed. No additional copy initialiation, etc.

If you implemented your assignment operator differently, as:

S& operator=(S /* no ref */) { return *this; }

Then an lvalue-to-rvalue conversion would happen in order actually invoke this function, and you would see your copy constructor get called.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Barry