Function Overloading
The standard library function pow is an example: it is available in an int version and a double version.
Why a Shared Name Causes Trouble
The bind() facility lets you take a function and fix some of its arguments, producing a new function object with fewer parameters. Most of the time you can pass the name of a function straight into bind() and everything works. It is natural to assume the same will hold when the name refers to several functions, since the compiler already knows how to choose between them in an ordinary call. That assumption is the most frequent source of confusion when binding overloaded functions, and this article examines it.
What do you think happens?
Two overloads of pow are declared: one takes two int arguments and one takes two double arguments. A single line binds the first argument of pow to a placeholder and fixes the second argument to 2, using the bare name pow. What do you expect the compiler to do with that line?
Reveal answer
The line cannot be resolved. bind() receives the bare name pow as a value, and that name refers to more than one function, so bind() has no single target to store.
An ordinary call to pow works because the arguments at the call site select the overload. bind() has no call arguments at the point where it needs to know the function, so the name alone is ambiguous.
One Name, Several Functions
Function overloading means having multiple functions with the same name but different parameter lists. Overloaded functions share a name and differ in their argument types.
The standard library function pow is an example: it is available in an int version and a double version. Both functions share the name pow and differ only in their parameter types. The name pow is therefore not one function that handles every numeric type. It is a set of alternatives, and each definition is a separate function with its own parameter list.
Why Ordinary Calls Succeed
When you call pow in an ordinary expression, the arguments you supply decide which overload is used. The call pow(2, 3) carries its argument types with it, so the compiler can match the call to one candidate. The difficulty with bind() is different. bind() receives the function name as a value to be stored, not as the arguments of a call. At the point where bind() needs to know which function to store, there are no call arguments to consult, so the name is ambiguous.
Fixing the bind Call
The fix is to state which version you want. The corrected line differs from the bare-name line by one cast that gives the compiler the exact function type to bind. Once that cast is in place, the name refers to exactly one function, and bind() can accept it. The form is correct but awkward to read, which is why it is worth learning to read it clearly.
auto f = std::bind(pow, std::placeholders::_1, 2);The bare overloaded name pow is passed to bind(). It names two functions, so bind() has no single target.
auto f = std::bind((double(*)(double,double))pow, std::placeholders::_1, 2);The cast selects the double version. The name now refers to one function, and the placeholder and the bound value 2 work as they do for any function.
How the Compiler Picks an Overload
For every call to an overloaded name, the compiler first collects the functions with that name. It then asks whether one of them is most appropriate for the argument types at that call. If one is, that function is invoked. If no alternative is better than the others, the call is ambiguous. The choice is made at each call site, so the same name can invoke different functions in different places in one program. The source describes this outcome but does not list the full ranking rules, so this article focuses on the observable results: which call succeeds and which is rejected.
- Three declarations share the name print and differ only in parameter types.
- The integer argument 42 matches the int overload.
- The string literal matches the string overload.
Choosing an overload from the argument type
Given the three overloads of print above, which function runs for the call print(42)?
- 1Collect candidates
All functions named print are candidates:
print(int),print(double), andprint(std::string). - 2Check argument type
The argument 42 is an integer.
- 3Find the most appropriate candidate
print(int)matches the argument type directly, so it is the most appropriate candidate. - 4Invoke it
The compiler invokes
print(int).
print(int) runs. The declaration order did not decide the choice; the argument type at the call site did.
When Two Overloads Tie
A call is ambiguous when two or more alternatives are equally suitable, and the compiler reports it as an error. Consider two overloads whose parameter types are swapped in position: one takes an int followed by a double, and the other takes a double followed by an int. Both take two arguments, but one expects the integer first and the other expects it second.
- Both overloads accept two arguments.
- For the arguments 0 and 0, neither overload is better than the other.
- The declarations themselves are valid. The error appears only at the call.
What do you think happens?
Two overloads are declared with parameter lists (int, double) and (double, int). A program makes the call print(0, 0). Will the compiler pick one overload, or will it report a problem?
Reveal answer
The compiler reports an error. Neither overload is better than the other for the arguments 0 and 0, so the call is ambiguous.
The declarations are not the problem. The problem appears only when a call uses them in a way that does not single out one function.
Mistakes With Overloaded Names
Common mistake Assuming bind() will pick the right overload from the name alone
What it looks likeauto f = std::bind(pow, std::placeholders::_1, 2);Why it failsbind()receives the name as a value to store. The name refers to more than one function, and there are no call arguments to decide which one.Do this insteadState the version explicitly with a function pointer cast before binding.
Common mistake Treating the name as one function that handles every type
What it looks likeprint(42) and print("D is for Digital") both use the name print.Why it failsprint is a set of separate functions. Each call selects one of them from its argument types.
Do this insteadThink of an overloaded name as a set of candidates, and check which candidate each call selects.
Common mistake Assuming a tie is resolved by declaration order
What it looks likeprint(0, 0) with overloads (int, double) and (double, int).Why it failsNeither overload is better for these arguments, so the call is ambiguous and the compiler reports an error.
Do this insteadChange the call or the overloads so that exactly one candidate is the most appropriate match.
Common mistake Reading the cast as a call to pow instead of a pointer type
Keeping Overloads Consistent
Write overloads so that every version follows the same meaning. The compiler chooses an overload by its argument types, so a reader must be able to rely on the same name meaning the same operation whichever version runs. Choose parameter lists that let each call match one candidate clearly, and avoid overload pairs whose parameter types are swapped in a way that makes common calls tie.
Trying the Binding Fix
A bind call uses the bare overloaded name pow with a placeholder for the first argument and fixes the second argument to 2. Rewrite it so that it binds the double version. Name the token that changes, and explain why the placeholder and the bound value do not need to change.
Hints
- Look at the function argument first. That is where the ambiguity sits.
- The cast gives the compiler the parameter types and the return type of the version to use.
- After the cast, the name refers to exactly one function, so the placeholder and the bound value work as they do for any function.
Key Takeaways
- An overloaded name is a set of functions that share one name and differ in parameter types, so the name alone does not identify one function.
bind()cannot resolve a bare overloaded name, because it receives the name as a value and has no call arguments to choose by.- An explicit function pointer cast, such as one naming the double version of pow, selects one overload so that
bind()can accept it. - The compiler chooses an overload at each call from the argument types, and reports an ambiguous call as an error when no candidate is better than the others.