Channel72
Channel72

Reputation: 24719

C++: Confusion over function overloading and order of declaration

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

Answers (1)

James Kanze
James Kanze

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

Related Questions