Search code examples
c++inheritancereferencelanguage-lawyerobject-slicing

Is object slicing by assigning through a base reference well-defined?


Is the object slicing that occurs when assigning a derived object through a base class reference (without a virtual operator=) a well-defined operation? I.e., is it guaranteed by the standard to leave the derived parts of the object untouched?

In many questions regarding object slicing here and elsewhere, an example like the following is given:

struct Base{
    int x;
    Base(int xx) :x(xx) {}
    virtual void print() const {std::cout << "Base("<<x<<")\n";}
};

struct Derived : Base{
    int y;
    Derived(int xx, int yy ) :Base(xx),y(yy){}
    void print() const {std::cout << "Derived("<<x<<","<<y<<")\n";}
};

int main()
{
    Derived d1{1,2};
    Derived d2{3,4};

    Base& br = d1;
    br = d2;     // assign a Derived through a Base&

    br.print();  // prints Derived(3,2)
}

The example is intended to show that when assigning through a Base& it only assigns the Base class members and leaves the members in Derived untouched.

Building the above example with -fsanitize=undefined does not complain, and it produces the expected output on all systems I've run it.

The question is, is this guaranteed by the standard? In my understanding it is, as Base::operator= cannot write outside the Base part of the object, and the Derived part of the object (here int y) cannot have any overlap with the Base part.

Is there some corner case I am missing? (There are of course many ways having an inconsistent object can lead to undefined behaviour, but my question is limited to what happens during the assignment operation.)


Solution

  • Base& br = d1;
    

    This creates a reference to a Base object. This will now be referencing a Base object that's just as well-formed as any other Base object (in a well-formed C++ program). In all respects, the referenced Base object is identical to all other Base objects that may or may not exist. This object can be assigned to just like any other Base object can be assigned to, and the exact same thing will happen to this object as to any other Base object that gets assigned to. The End.

    Objects that are base objects of other objects are not any different than objects that are not, when viewed through the prism of those objects alone. They are well-formed objects of their own, and are just like any other Base object (ignoring things like virtual methods here). This is a fundamental concept in C++.

    It would be rather inconvenient if assigning to a Base object suddenly "writes" somewhere else. It would be rather difficult to accomplish anything in C++, in that case. Of course, it goes without saying that one can always overload operator=, and then the sky's the limit. But from a practical point, as long as the = operator works the way it's expected to work, assigning to a Base object won't do anything to anything that's not a part of that Base object.