ShanZhengYang
ShanZhengYang

Reputation: 17631

How is the C++ compiler's performance affected by defining several functions with the same name but different types?

I ran across this example in Stroustrup's "A Tour of C++" (2014). If you define functions with the exact same name but different argument types, the compiler will select the most appropriate function for each call:

void print(int);      // takes an integer argument
void print(double);   // takes a floating-point argument 
void print(string);   // takes a string argument

void user() 
{
    print(42);                  // calls print(int) 
    print(9.65);                // calls print(double) 
    print("D is for digital");  // calls print(string)
}

(1) Doesn't naming functions with the same name like this result in a (perhaps minor) performance hit?

(2) How exactly does the compiler "select" the most appropriate function, given the input? What is going on behind the scenes here?

Upvotes: 1

Views: 325

Answers (3)

Nick Matteo
Nick Matteo

Reputation: 4553

(1) There is no performance hit at runtime, because the compiler figures out which function to call and calls it, just the same as any other function.

(2) Name mangling. In your example, when you write

void print(int);      // takes an integer argument
void print(double);   // takes a floating-point argument 
void print(string);   // takes a string argument

the compiler gives them different names based on their parameter types, perhaps:

void print_i(int);      // takes an integer argument
void print_d(double);   // takes a floating-point argument 
void print_str(string);   // takes a string argument

Then when you call print(42), it says "Oh, it's called with an int, so let's actually call print_i(42)."

Upvotes: 1

arhzu
arhzu

Reputation: 570

(1) In an ideal case, there is no extra cost. However, if the functions are exported by a shared library, there might be a larger run-time cost due to symbol resolution when the function is called for the first time. This is fully dependent on the dynamic linker implementation.

For example, if the dynamic linker in some exotic environment uses a simple hash table with a bad hash function (e.g. considers only the prefix of a symbol), hash collisions might make each lookup take O(n) time.

Upvotes: 0

Jerry Coffin
Jerry Coffin

Reputation: 490148

Function overloading (at least normally) has no effect on execution speed. Consider if you wrote these functions as:

void print_int(int);         // takes an integer argument
void print_double(double);   // takes a floating-point argument 
void print_string(string);   // takes a string argument

...and then printed things out by choosing one of them based on what you wanted to print. That's pretty much what the compiler does: it takes the number and type of parameters, and encodes them into a "mangled" name. When you make a function call, the compiler (at compile time) looks through the available functions, chooses one, and creates a call to that function. The code to call the function is identical to code for calling a function that hasn't been overloaded.

Selecting the best function is a non-trivial exercise, to put it mildly. It takes place in a few phases. The first is to find a scope at which there's something defined with the name you've used. The second is to look through everything at that scope with that name to create a list of overloads.

The next step is to eliminate overloads that couldn't possibly work at all--e.g., overloads that simply can't accept the number of arguments you've passed. If that leaves only one function, overload resolution is done. If there's more than one, we get to the final (and trickiest) part of overload resolution.

The compiler starts with a list of possible implicit conversions for each type, and a ranking for each. The "best" is identity conversion (i.e., no conversion at all, such as if you're passing an int and the function expects an int). Slightly worse (but still fairly good) is something like adding a const. Eventually, you get to things like truncating a double to an int.

The compiler then goes through arguments one at a time, and looks at the conversion necessary to get from that argument to the type for the formal parameter. To qualify as the "best" overload and be chosen for use, at least one argument must have a "better" conversion than the conversion for any other overload, and none of the arguments can have a conversion that's worse than the overload that would be needed for any other overload.

If there is no such thing (e.g., you have only two viable functions, and each has one parameter that has a better conversion, and one argument with a worse conversion) the call is ambiguous, so the code can't compile.

Upvotes: 2

Related Questions