I'm working on some code involving "real-life" as opposed to program-only entities. Let's say that it's a camel-handling library. Now, my library has two camel reference-type classes: Camel
and SingingCamel
. Camel
is a concrete class with no virtual members; and SingingCamel : public Camel { }
is a camel which can sing, and programming-wise, has no overrides/shadowing. There is no problem treating a singing camel like a non-singing one.
Now, since these are both reference-type rather than value-type classes, I would rather pass them around by value. Unfortunately, if I write:
void have_joyride(Camel camel, Person person)
(remember - these are reference classes; this does not create a new camel or new person)
... then I am slicing my SingingCamel
.
The C++ core guidelines say:
ES.63: Don't slice
Slicing -- that is, copying only part of an object using assignment or initialization -- most often leads to errors because the object was meant to be considered as a whole. In the rare cases where the slicing was deliberate the code can be surprising.
Now, it's not that I can't avoid slicing. I could certainly write:
void have_joyride(Camel const & camel, Person const & person);
and that would work fine. ... except that, in this case, I would actually need to have four functions:
void have_joyride(Camel const & camel, Person const & person);
void have_joyride(Camel const & camel, Person && person);
void have_joyride(Camel && camel, Person const & person);
void have_joyride(Camel && camel, Person && person);
and this violates another C++ core guideline:
ES.3: Don't repeat yourself, avoid redundant code
Duplicated or otherwise redundant code obscures intent, makes it harder to understand the logic, and makes maintenance harder, among other problems. It often arises from cut-and-paste programming.
as well as this one:
F.16: For "in" parameters, pass cheaply-copied types by value and others by reference to const
Both let the caller know that a function will not modify the argument, and both allow initialization by rvalues.
And so I ask myself:
To slice, or not to slice, that is the question:
Whether 'tis nobler in the mind to suffer
The lrefs and rrefs of outrageous overloads,
Or to take values from inheriting reference objects,
And by passing, slice them.
Notes:
- No camels were sliced during the writing of this question!
- Related, more general treatment: Where should I prefer pass-by-reference or pass-by-value?