Reputation: 8220
Take the following code:
#include <iostream>
struct Base {
char x = 'b';
};
struct Derived : Base {
operator Base() { return Base { 'a' }; }
};
int main() {
Derived derived;
auto base = static_cast<Base>(derived);
std::cout << "BASE -> " << base.x << std::endl;
}
Under both g++ and clang++, this produces:
BASE -> b
I was expecting the following:
BASE -> a
Why? Because why I read this code, I see a conversion operator inside of Derived
that returns an instance of Base
containing 'a'
.
clang++ did me the courtesy of emitting a warning:
main.cpp:9:5: warning: conversion function converting 'Derived' to its base class 'Base' will never be used
operator Base() { return Base { 'a' }; }
Researching this warning, I found that this was by design (paraphrased for clarity):
The type of the conversion function ([dcl.fct]) is “function taking no parameter returning conversion-type-id”. A conversion function is never used to convert a (possibly cv-qualified) object [...] to a (possibly cv-qualified) base class of that type (or a reference to it) [...].
So it would seem that both compilers are doing the right thing here. My question is, why does the standard require this behaviour?
Upvotes: 4
Views: 148
Reputation: 119847
The designer of C++ have decided that this general principle should apply (Stroustrup, The C++ Programming language, chapter 18)
User-defined conversions are considered only if a call cannot be resolved without them (i.e., using only built-in conversions).
For better or worse, there is a built-in conversion from Derived to Base, so the user-defined operator is never considered.
The standard generally follows the path devised by Stroustrup unless there is a very good reason not to.
Stroustrup cites the following rationale for the overall type conversion design (ibid.):
The rules for conversion are neither the simplest to implement, nor the simplest to document, nor the most general that could be devised. They are, however, considerably safer, and the resulting resolutions are typically less surprising than alternatives. It is far easier to manually resolve an ambiguity than to find an error caused by an unsuspected conversion.
Upvotes: 3
Reputation: 302748
Let's I have a hierarchy of Animal
s and I want to write a function that takes an modifiable Animal
, without slicing. How would I do that? I have two options:
void by_ref(Animal& );
void by_ptr(Animal* );
Which I could call like:
Dog dog = ...;
by_ref(dog);
by_ptr(&dog);
Today, the only difference between those two calls is going to be the syntax used inside of the two functions and possibly a check against nullptr
. This is because the Dog
to Animal&
and Dog*
to Animal*
are guaranteed standard derived-to-base conversions. There is no alternative.
But imagine if I could actually write:
struct Dog : Animal {
operator Animal&() { ... };
};
Now those two calls could do totally different things! My Dog*
to Animal*
conversion is still the same dog, but my Dog
to Animal&
conversion is an entirely different Dog
. It could even be a Cat
. Which would make all of this code basically impossible to reason about.
You would need a special mechanism to definitely give you the base subobject of a particular type:
by_ref(std::base<Animal>(dog));
which would basically have to be used everywhere to guarantee correctness of any code that relies upon inheritance. Which is a lot of code.
And to what benefit? If you want a different base subobject, you can just write a differently named function:
struct Dog : Animal {
Animal& foo();
};
Naming may be one of the two hard things about programming, but better to just come up with your own name than to open up the bag of deplorables that would be allowing people to write their own derived-to-base-but-not-really conversions.
Upvotes: 5
Reputation: 473262
If you could override the conversion-to-base-class in C++, then you could break lots and lots of stuff. For example, how exactly would you be able to get access to the actual base class instance of a class? You would need some baseof
template, similar to std::addressof
that's used to bypass the il-conceived operator&
overload.
Allowing this would create confusion as to what code means. With this rule in place, it's clear that converting a class to its base class copies the base class instance, in all cases.
Upvotes: 6