3

I thought that in a nested namespace, anything that is part of the parent (or global) namespace is considered equally for overload resolution, but this example seems to show otherwise.

This works fine:

#include <iostream>

void foo(int) { std::cout << "int\n"; }
void foo(float) { std::cout << "float\n"; }

namespace NS {
    void bar() {
        foo(0);
    }
}

int main() {
    NS::bar();
}

The call to foo(0) matches foo(int) since it is a better match and everything works as expected. However, if I move the declaration of foo(float) into the namespace:

#include <iostream>

void foo(int) { std::cout << "int\n"; }

namespace NS {
    void foo(float) { std::cout << "float\n"; }
    void bar() {
        foo(0);
    }
}

int main() {
    NS::bar();
}

The call to foo(0) now calls foo(float)!

I have searched through https://en.cppreference.com/w/cpp/language/overload_resolution and many other such pages to find the rule that explains this, but I seem to be missing it. Can someone please explain which of the many complex overload resolution rules causes this, or is it something else?

EDIT
I just discovered it is even weirder. Even if the foo inside the namespace doesn't match at all, it still won't use the one outside. This just completely fails to compile:

#include <iostream>

void foo(int) { std::cout << "int\n"; }

namespace NS {
    void foo(float, float) { std::cout << "float\n"; }
    void bar() {
        foo(0);
    }
}

int main() {
    NS::bar();
}
songyuanyao
  • 169,198
  • 16
  • 310
  • 405
Baruch
  • 20,590
  • 28
  • 126
  • 201
  • You think that's weird? You haven't considered the alternative. If an overload set was always formed from everything in sight, modifying your program even slightly would not be possible. Imagine you include a new header for some utility, but then other unrealted functions in the file suddenly work differently! Simply because the new header included better matches in overload resolution. – StoryTeller - Unslander Monica Aug 04 '20 at 13:47

1 Answers1

7

The point is name lookup, which happens before overload resolution.

When the name foo is found at the namespace NS, name lookup stops, the further scopes won't be checked, the global foo won't be found at all. Then there's only one candidate in overload resolution, and int could convert to float implicitly, then NS::foo(float) gets called at last.

(emphasis mine)

name lookup examines the scopes as described below, until it finds at least one declaration of any kind, at which time the lookup stops and no further scopes are examined.

songyuanyao
  • 169,198
  • 16
  • 310
  • 405