I think a good general rule for copy construction which "casts" or converts between subclasses, if you are providing a generally reusable base class, is this:
base class copy constructor should be able to take subclass instances as an argument, and construct a base class instance with all the common base class state the same; and
subclass copy constructor should be able to take base class instances as an argument, and construct a subclass instance with all the common base class state the same; but
for any two classes where one is not a subclass of the other, even if they share a common base class, the copy constructor which they inherit from that base should not let them automatically "cast" each other like that.
In other words, given a base class `Foo` provided by some library, and subclasses `Bar` and `Qux` which both singly directly inherit from `Foo`, these should work out of the box:
Foo(bar_instance)
Foo(qux_instance)
Bar(foo_instance)
Qux(foo_instance)
But these should not work unless `Bar` or `Qux` go out of their way to make them work, for example by overloading the relevant constructor:
Bar(qux_instance)
Qux(bar_instance)
Because when converting between a base and a subclass, we know that the subclass is supposed to be substitutable for the base. The subclass has some delta of behavior, constraints, or data shape versus the base. There can be a pretty sensible default semantic for what it means to convert between them - "I got given a base instance, I want to use my subclass behavior with it" or "I may have been given a subclass instance, and I intend to ignore or remove any extra unexpected behavior from it". And this is generally a more useful default than rejecting the conversion.
But when converting between two independent subclasses of the same base - trying to jump between branches of the class hierarchy - well those are not expected to be substitutable. The deltas they made to the base might be incompatible. There is no sensible default conversion - each of those subclasses represents needs specific to the maker of that subclass, and we can't know if conversion is appropriate or how to do it without knowing those unique needs for both subclasses. And the only doable default that the base class can provide for this case would be to lose any functionality and invariants provided by one subclass, and then layer on all the functionality and constraints of the other - as if you converted from one subclass to the base class and then to the other subclass - but providing that out-of-the-box seems too likely to turn accidental erroneous or incompatible mixing of subclasses into silent misbehavior.
Note that if you actually do want the behavior described in the last paragraph, then the behavior from the previous paragraph lets you explicitly do it:
Bar(Foo(qux_instance)
Qux(Foo(bar_instance)
This explicitly bottlenecks through the common base, self-documents and announces the fact that the conversion loses or misses anything that wasn't shared by the common base class, which is good because in most cases this is exactly where this should be visible, where it is most local to the code whose behavior it is most relevant to.















