Classes Can Provide Type Conversion
We just learned that C-style strings (which have type
const char*
) are automatically converted intostd::string
when we pass them into a function expecting astd::string
.Yes, and I want to know how to do that for my own classes!
Please tell us MORE!!
Conversion Into an Instance of Class
If a class provides a single-argument constructor, C++ will consider that constructor to be a way to automatically convert a value into that type.
So it just happens automatically? No new thing to learn? No special syntax?
That's right. If you write a single-argument constructor, you've automatically added type conversion, too.
For example, suppose we have a FuzzyBoolean
class that supports the idea of Yes/No/Maybe and it has a constructor for FuzzyBool
that takes a bool
:
class FuzzyBool {
// ...
FuzzyBool(bool preciseBool);
// ...
};
Also, suppose that we have written this function:
void makeDinner(FuzzyBool opinion) {
if (opinion.positiveEnough()) {
// make the dinner
} else {
// wait
}
}
We can call makeDinner()
with a FuzzyBool
variable constructed using a boolean value as
FuzzyBool wantDinner{true};
makeDinner(wantDinner);
or we could call makeDinner()
with a temporary FuzzyBool
value from a boolean value as
makeDinner(FuzzyBool{true});
But we can also just say
makeDinner(true);
and C++ will implicitly make a temporary FuzzyBool
object exactly as the previous code example did (explicitly) to automatically convert our true
into a FuzzyBool
object.
This automatic conversion occurs if makeDinner()
takes the FuzzyBool
by copy (as shown) or by constant reference.
But arguments that are passed by non-const
reference (i.e., are writable) don't have automatic type conversion.
Why not? Why not have type conversion for all the things?
It would probably be the wrong thing to do to create a temporary object, change it, and then just throw that object away. So to avoid surprising bugs, C++ doesn't do that.
If we do want to do that for a function that writes to the argument, we have to do it by hand, and explicitly initialize a named variable like we did with
wantDinner
above.
Conversion Out of an Instance of Class
What if we wanted to go the other way, and automatically turn
FuzzyBool
objects into regularbool
s? We can't add a single-argument constructor to thebool
class—it isn't even a class.Yes, there's a way to do that, too.
FuzzyBool
can define a type-cast operator that defines how to turn it into a bool
.
The type-cast operator is defined as a special member function (a type-conversion operator) whose name matches the name of the type you want to be able to convert to.
Declaration
class FuzzyBool {
// ...
operator bool();
// ...
};
Definition
FuzzyBool::operator bool() {
return state_ != NO;
};
That looks pretty weird. It looks a lot like a function, but it doesn't have a return type.
It does look a bit odd. The return type is already the function's name, so we don't have to say it twice.
In any case, you won't need to write code like that in this class.
Avoiding Unintended Conversion
Having type conversion happen automatically without asking might not always be what I want. Can I turn it off?
Yes!
The explicit
keyword will ensure that a conversion constructor or type-cast operator is only used when you explicitly ask for it.
Explicit Parameterized Constructor
An explicit
parameterized constructor will only be called in a context that looks like a constructor call.
In other words, if FuzzyBool
has an explicit
constructor that takes a bool
,
class FuzzyBool {
// ...
explicit FuzzyBool(bool preciseBool);
// ...
};
then
makeDinner(FuzzyBool{true});
will work, but
makeDinner(true);
will not.
Explicit Type-Cast Operator
An explicit type-cast operator will only be called if we explicitly ask for the conversion.
In other words, if FuzzyBool
has an explicit
type-cast operator bool()
,
class FuzzyBool {
// ...
explicit operator bool();
// ...
};
then
bool doit = bool(myFuzzyBool);
will work, but
bool doit = myFuzzyBool;
will not.
Limits on Conversion
Suppose we have a Person
class with constructors,
Person(const string& name);
Person(int age);
and a Cow
class with a member function,
Cow::addRider(const Person& rider)
What happens if bessie
is a Cow
and we say each of the following?
For each argument to a function, the compiler will perform a single conversion.
It won't even try to do multistep conversions; the potential chain of conversions would just be too long.
But… isn't this just a single conversion, from the string
"Chris"
to aPerson
named Chris?Remember that string literals are C-style strings. Type conversion turns them into
std::string
s.
A Place Where Type Conversion Doesn't Apply
There are lots of places where the compiler will implicitly convert types, but here's another place that it won't.
Suppose the Cow
class has a feed
member function, and a constructor that takes a std::string
. So you can write
std::string s = "Bessie";
Cow bessie{s};
bessie.feed();
If you write
s.feed();
you will get a compiler error—the compiler won't transform the code into
Cow{s}.feed(); // You could write this; C++ won't.
Why do you think that is?
I don't know why, but I'm pretty glad it doesn't. It'd feel wrong somehow.
Can we be more specific?
Well, we'd be feeding a temporary object and throwing it away, which probably isn't what we want.
Indeed!
And to do that conversion, the compiler would have to look at every single class it knows about, see which one(s) can be created from a string, and then see which of those have a
feed
member function.Yes, that does seem a bit unreasonable, too.
(When logged in, completion status appears here.)