Preamble
Overload resolution in C++ can be an overly complex process. It takes quite a lot of mental effort to understand all of the C++ rules that govern overload resolution. Recently it occurred to me that the presence of the name of an overloaded function in the argument list can add to the complexity of overload resolution. Since it happened to be a widely used case, I posted a question and received an answer that allowed me to better understand the mechanics of that process. However, the formulation of that question in the context of iostreams seems to have somewhat distracted the focus of the answers from the very essence of the problem being addressed. So I started delving deeper and came up with other examples that ask for more elaborate analysis of the issue. This question is an introductory one and is followed by a more sophisticated one.
Question
Assume that one fully understands how overload resolution works in the absence of arguments that are themselves names of overloaded functions. What amendments must be made to their understanding of overload resolution, so that it also covers cases where overloaded functions are used as arguments?
Examples
Given these declarations:
void foo(int) {}
void foo(double) {}
void foo(std::string) {}
template<class T> void foo(T* ) {}
struct A {
A(void (*)(int)) {}
};
void bar(int x, void (*f)(int)) {}
void bar(double x, void (*f)(double)) {}
void bar(std::string x, void (*f)(std::string)) {}
template<class T> void bar(T* x, void (*f)(T*)) {}
void bar(A x, void (*f2)(double)) {}
Below expressions result in the following resolution of the name foo
(at least with gcc 5.4):
bar(1, foo); // foo(int)
// but if foo(int) is removed, foo(double) takes over
bar(1.0, foo); // foo(double)
// but if foo(double) is removed, foo(int) takes over
int i;
bar(&i, foo); // foo<int>(int*)
bar("abc", foo); // foo<const char>(const char*)
// but if foo<T>(T*) is removed, foo(std::string) takes over
bar(std::string("abc"), foo); // foo(std::string)
bar(foo, foo); // 1st argument is foo(int), 2nd one - foo(double)
Code to play with:
#include <iostream>
#include <string>
#define PRINT_FUNC std::cout << "\t" << __PRETTY_FUNCTION__ << "\n";
void foo(int) { PRINT_FUNC; }
void foo(double) { PRINT_FUNC; }
void foo(std::string) { PRINT_FUNC; }
template<class T> void foo(T* ) { PRINT_FUNC; }
struct A { A(void (*f)(int)){ f(0); } };
void bar(int x, void (*f)(int) ) { f(x); }
void bar(double x, void (*f)(double) ) { f(x); }
void bar(std::string x, void (*f)(std::string)) { f(x); }
template<class T> void bar(T* x, void (*f)(T*)) { f(x); }
void bar(A, void (*f)(double)) { f(0); }
#define CHECK(X) std::cout << #X ":\n"; X; std::cout << "\n";
int main()
{
int i = 0;
CHECK( bar(i, foo) );
CHECK( bar(1.0, foo) );
CHECK( bar(1.0f, foo) );
CHECK( bar(&i, foo) );
CHECK( bar("abc", foo) );
CHECK( bar(std::string("abc"), foo) );
CHECK( bar(foo, foo) );
}
Let's take the most interesting case,
The primary question to figure out is, which overload of
bar
to use. As always, we first get a set of overloads by name lookup, then do template type deduction for each function template in the overload set, then do overload resolution.The really interesting part here is the template type deduction for the declaration
The Standard has this to say in 14.8.2.1/6:
(
P
has already been defined as the function template's function parameter type including template parameters, so hereP
isvoid (*)(T*)
.)So since
foo
is an overload set containing a function template,foo
andvoid (*f)(T*)
don't play a role in template type deduction. That leaves parameterT* x
and argument"abc"
with typeconst char[4]
.T*
not being a reference, the array type decays to a pointer typeconst char*
and we find thatT
isconst char
.Now we have overload resolution with these candidates:
Time to find out which of these are viable functions. (1), (2), and (5) are not viable because there is no conversion from
const char[4]
toint
,double
, orA
. For (3) and (4) we need to figure out iffoo
is a valid second argument. In Standard section 13.4/1-6:For overload (3) of
bar
, we first attempt type deduction forwith target type
void (*)(std::string)
. This fails sincestd::string
cannot matchT*
. But we find one overload offoo
which has the exact typevoid (std::string)
, so it wins for the overload (3) case, and overload (3) is viable.For overload (4) of
bar
, we first attempt type deduction for the same function templatefoo
, this time with target typevoid (*)(const char*)
This time type deduction succeeds, withT
=const char
. None of the other overloads offoo
have the exact typevoid (const char*)
, so the function template specialization is used, and overload (4) is viable.Finally, we compare overloads (3) and (4) by ordinary overload resolution. In both cases, the conversion of argument
foo
to a pointer to function is an Exact Match, so neither implicit conversion sequence is better than the other. But the standard conversion fromconst char[4]
toconst char*
is better than the user-defined conversion sequence fromconst char[4]
tostd::string
. So overload (4) ofbar
is the best viable function (and it usesvoid foo<const char>(const char*)
as its argument).