Reputation: 24719
I realize that C++ inherited many requirements from C, and one of those is that the compiler won't recognize a global function unless it has previously encountered a prototype or definition for this function.
This also effects C++ function overloading. If there is more than one candidate for a function call, the "correct" candidate won't be included in the selection process if the compiler hasn't already seen a prototype/definition for it.
Consider:
void foo(int v)
{
cout << "Int version" << endl;
}
template <class T>
void call_foo(const T& v)
{
foo(v);
}
void foo(const std::string& v)
{
cout << "Overload for string" << endl;
}
Here, a call to call_foo(std::string("abc"))
will result in a compiler error, even though there is an overload of foo
for std::string
. The problem is that the function template call_foo
was defined before the compiler sees the overload.
However, this doesn't seem to apply to global operator overloads. We regularly overload std::ostream& operator << (std::ostream& os, const T&);
to make our custom types compatible with C++ ostreams. The compiler selects the correct overload, regardless of where the operator overload function is defined.
For example:
struct Bar { };
template <class T>
void dispatch(const T& v)
{
std::cout << v << std::endl;
}
std::ostream& operator << (std::ostream& os, const Bar& b)
{
os << "Outputting Bar...";
return os;
}
Here, if we call dispatch(Bar())
, the compiler calls the correct overload and outputs Outputting Bar...
.
So, it seems the C++ standard allows more advanced behavior when it comes to selecting candidate functions for operator overloading.
My question is, why wasn't this ability extended to regular function overloading? I realize there is the requirement of backwards compatibility with C, but this wouldn't really have any bearing on that because as soon as you write a function overload you're not writing a C program anyway.
Upvotes: 2
Views: 414
Reputation: 153909
First, there's no need for the compiler to see a definition when it
practices overload resolution; a declaration suffices. Second, the
issue is a lot more complex than you seem to realize. In your function
template call_foo
, foo
is a dependent name, because it is used in a
context which depends on the instantiation type. That means that it
will be looked up twice, once at the point where the template is
defined, and a second time where the template is instantiated. This
second lookup, however, is purely ADL; it won't find a declaration not
brought in by ADL. In your case, the foo
you want is in the global
namespace, but when calling it with std::string
, the only namespace
considered by ADL is std::
.
In your second example, Bar
is in global namespace, so declarations in
global namespace at the point of instantiation will be considered.
(Note that if dispatch
were not a template, it wouldn't be
considered.)
Upvotes: 5