The other day, I discovered a bug in some code that can be simplified to something like this:
A library of functions that handles a lot of different types:
void doSomethingWith(int i) { cout << "int" << endl; }; void doSomethingWith(double d) { cout << "double" << endl; }; void doSomethingWith(const string& s) { cout << "string" << endl; }; void doSomethingWith(const MyType& m) { cout << "MyType" << endl; };
Used like this:
doSomethingWith(3); doSomethingWith("foo");
It of course outputs:
int string
Then someone wanted to handle void pointers as well, and added this function to the library:
void doSomethingWith(const void* i) { cout << "void*" << endl; };
What is the output now? Make up your mind before looking.
int void*
What happened? Why did C++ decide to use the const void *
function instead of const string&
that we wanted it to use?
The type of a string literal is not string
, but const char[]
. When deciding on the overloaded function to use, C++ will first see if any of them can be used directly. A const void*
is the only type in our example than can point directly to the const char[]
, so that one is picked.
Before that function was introduced, none of the functions could be used directly, as neither const string&
nor const MyType&
can refer to a const char[]
, and it cannot be cast to an int
or a double
. C++ then looked for implicit constructors that could convert the const char[]
into a usable type, and found std::string::string(const char * s)
. It then went on to create a temporary std::string
object, and passed a reference to this object to void doSomethingWith(const string& s)
, like this:
doSomethingWith(std::string("foo"))
But then, when the const void*
version appeared as an alternative, it preferred to use that one instead as it could be used without constructing any temporary objects.
As usual, the code for this blog post is available on GitHub.
If you enjoyed this post, you can subscribe to my blog, or follow me on Twitter.
While this type of problem might occur in C#, you wouldn’t need to in this case since it has rich built-in support for working with types. You have `System.Object.GetType()`, the `typeof`, the `is` and the `as` keyword as well as `System.Type.GetTypeCode()` that makes working with types quite enjoyable.
Good one :)
Nice! We have the
typeid
operator. If you have a pointer toBase
pointing to an object ofDerived
, it will returnDerived
, but only if one of the methods inBase
is declared asvirtual
, otherwise it returnsBase
.We also have C-style cast,
dynamic_cast
,static_cast
and then of coursereinterpret_cast
which can cast between entirely unrelated pointers.MyClass obj; double* dp = reinterpret_cast<double*>(&c);
? No problem!As you can see, working with types in C++ is also a lot of fun! :)
It’s fun for sure, but for a whole bucketload of different reasons! Personally, I hate the flexibility of C++, especially since the “safety” of the compiler doesn’t really make me safe from anything and at the same time adds the inflexibility of static typed languages that require compilation. :)
I am not sure I get you, exactly how does the compiler not make you safe from anything? Can you give a few examples?
Good post.