C++ Tricky Overloads

Function and method overloading is certainly a powerful C++ feature. Overloading can help simplifying code in several contexts.

However, there are some “gotchas” and some tricky aspects of function/method overloading that must be considered, to prevent slipping on a banana peel.

For example, let’s consider a class that might represent a container of some data. This class has a couple of overloaded methods to add a new item to the container:

 

#include <iostream>  // for std::cout
#include <string>    // for std::string
using namespace std; // sample demo/test code

class Container
{
public:

  // ...
  
  void AddItem(int index, bool value)
  {
    cout << "AddItem(int, bool)\n";
  }

  void AddItem(int index, const string& value)
  {
    cout << "AddItem(int, const string&)\n";
  }
};

In particular, the AddItem() method is overloaded with a couple of variants: one taking a bool value as the last parameter, and another one taking a string value.

The following code simply adds a new string to an instance of the container class:

 

int main()
{
  Container c;
    
  string str{"Connie"};
  c.AddItem(1, str);

This works fine: the AddItem(int, const string&) overload is picked by the compiler, and – assuming the implementation works correctly – “Connie” goes into the container as a new string item.
We might think of adding another string with some innocent-looking code like this (I mean, we already have a string literal, so why bothering creating a std::string instance in the first place?):

   c.AddItem(2, "Sandy");
} // main

And here we have a problem!

In fact, if we compile and run a simple test program containing the above code fragments, we get the following output:

AddItem(int, const string&)
AddItem(int, bool)

So, as we can note, the second AddItem call (i.e. c.AddItem(2, “Sandy”)) is actually translated by the compiler as a call to the bool overload, not to the string overload (which probably almost everyone would have expected, at least at a first glance). Is this a compiler bug? Should we file a bug report?

Well, unfortunately: No! The C++ compiler is doing its job just fine: this behavior is just by design!

So, why does an apparently correct looking code like c.AddItem(2, “Sandy”) make the compiler choose the bool overload (instead of the string overload)??

To understand that, we have to talk a little bit about conversions, in particular, user-defined conversions and built-in conversions.

So, a std::string instance can be constructed from the string literal “Sandy”, considering the “const char*” std::string’s constructor overload. This is a user-defined conversion, implemented in a converting constructor.

However, a “const char*” (i.e. the type associated by the compiler to the string literal) can also be converted to a bool: this is an example of the so called pointer-to-bool conversion. And this one is a built-in standard conversion, not a user-defined conversion.

Crossroad
Crossroad

So, the C++ compiler has to choose between two possible conversions: a user-defined conversion vs. a built-in conversion. According to the C++ rules, given this choice, the built-in conversion is preferred (the rationale is that C++ considers a standard built-in conversion to be cheaper than calling a converting constructor, which implements a user-defined conversion).

Now, in all honesty, I consider it very intuitive that code like c.AddItem(2, “Sandy”) would pick the string overload. And this was just a small repro, but imagine what kind of bugs and confusions can be generated in more complex code bases! To avoid these kinds of problems, I’d give up the overloading feature in cases like this, and would simply rename methods to be more type-specific. For example, instead of an overloaded bug-prone AddItem(), I would prefer having several methods like: AddString(), AddBool(), etc.

This may appear more verbose and less elegant at a first glance, but it will certainly save lots of time and headache in terms of debugging code later.

Simple or complex
Simple or complex