Varsuuk
Varsuuk

Reputation: 29

Why does using ranges::sort work in a function but not at global namespace when trying to salve ambiguity in calling sort()?

I last used C++ at the 98 with boost/C++11 simulation. I am reading Stroustrup's "A Tour of C++, Third Edition" to help introduct the things I will need to learn more about. Following through the chapters, I got to p122, 9.3.2 "The ranges namespace" section. In there I was wondering if the "use ranges::sort;", where it was inserted, would cause the same ambiguity if the iterator version appeared after it. It turns out NOT, which ran counter to my expectation.

My question here is geared to understand >why< it works there but (to my untrained mind) why it doesn't if do it at the global level?

// The example in the book does not include the includes

#include <algorithm>
#include <iostream>
#include <vector>

using namespace std;
using namespace std::ranges; // (1)
// using std::ranges::sort; // (4)


void g(vector<int>& v) {
// using ranges::sort;  // (3)
  sort(v.begin(), v.end()); // Error: Ambiguous
// using ranges::sort;  // (2)
  sort(v);  // Error: Ambiguous
}

The code above is from the book except for the lines with // (#) - the line with (2) appears in the book in the solution block.

If we simply comment out (1) and uncomment (2) the error goes away. I thought I understood why, but it appears I did not.

If we leave (2) commented out and instead uncomment (3), it ALSO works. So, it isn't a matter of where it is introduced in the FUNCTION at least.

But if I uncomment (4) (leaving (2) & (3) commented out) the same error as we saw with just (1) occurs - ambiguity.

Can anyone explain that or point me to a nice clear explanation of what's at play here?

Upvotes: 2

Views: 120

Answers (1)

Quuxplusone
Quuxplusone

Reputation: 27360

Executive summary: You're probably thinking that the compiler is complaining about overload resolution ambiguity, i.e. "Which function in this overload set is the best match?" It is not. It's complaining about name lookup ambiguity, i.e. "Does the name sort in this context even refer to an overload set to begin with?"


The first thing to understand is the difference between a using-directive like using namespace std::ranges and a using-declaration like using std::ranges::sort. A using-declaration is easy: it just acts like a declaration of the given name, right there in whatever scope you put it in. So:

int f(int);
namespace N { int f(char); }
namespace M { int test() { using N::f; f(42); } }

will definitely call N::f(char), not ::f(int), because the using-declaration for f inside the scope of test shadows or hides the declaration of ::f at global scope.

Using-directives are much weirder; see "How do C++ using-directives work?" (2020-12-21) and Why does C++'s "using namespace" work the way it does? — basically they pull the relevant declarations into the least common ancestor scope, so that

int f(int);
namespace N { int f(char); }
namespace M { int test() { using namespace N; f(42); } }

will end up doing overload resolution between ::f(int) and N::f(char) (and choosing the former).


The next thing to understand is that ranges::sort is not a function. It's a global variable. (Technically it's a thing called a "niebloid," which is specified as if it were a weird kind of function template that doesn't participate in ADL and has a few other quirks; but in reality the only way to implement such an animal is by making it a global variable, so that's what everyone does. There is an active proposal (P3136 "Retiring niebloids") to clean up the specification to match what actually happens in practice.)

Normally, you can't declare a variable and a function with the same name in the same scope. This doesn't compile:

int x;
int x();

And neither does this:

int x;
struct S { friend int x(S&); };

And neither does this:

namespace M { int x; }
namespace N { int x(); }
using M::x;
using N::x;  // error: redeclaration as a different kind of symbol

But this does compile!

namespace M { int x; }
namespace N { int x(); }
using namespace M;
using namespace N;

However, after you do this, things will behave as if there are declarations of both int x; and int x(); in the least-common-ancestor scope. Which is a very weird state to be in. Basically, any attempt to use the name x after this point will come out ambiguous. (Assuming the name lookup finds the conflicting xs in the least-common-ancestor scope, that is. If you've shadowed those declarations with some other more-local x, then of course there's no problem yet.)


That answers your first question:

using namespace std;
using namespace std::ranges;
void g(vector<int>& v) {
  // Doing anything with the name `sort` here gives an ambiguous lookup.
}

"If we simply comment out (1) and uncomment (2) the error goes away."

using namespace std;  // pulls `sort` into the global namespace
void g(vector<int>& v) {
  sort(v.begin(), v.end()); // OK, finds `sort` in the global namespace

  using ranges::sort;
  sort(v);  // OK, finds the local using-declaration;
     // the local declaration shadows the `sort` in the global namespace
}

"If we leave (2) commented out and instead uncomment (3), it ALSO works." Right; this is the clearest version. The only trick here is that when you say using ranges::sort, the compiler needs to know what you mean by ranges. (You didn't say "the ranges that belongs to namespace std"!) But fortunately there is a name ranges in the global namespace now — pulled into there by our using namespace std directive — so that's OK.

using namespace std;
void g(vector<int>& v) {
  using ranges::sort;  // OK, finds `ranges` in the global namespace
  sort(v.begin(), v.end()); // OK, finds the local using-declaration
  sort(v);                  // OK, finds the local using-declaration
}

"But if I uncomment (4) (leaving (2) & (3) commented out) the same error as we saw with just (1) occurs - ambiguity."

using namespace std;
using std::ranges::sort;
void g(vector<int>& v) {
  sort(v.begin(), v.end()); // Same problem: lookup finds both
    // a function named `sort` in the global namespace (from the using-directive) and
    // a non-function named `sort` in the global namespace (from the using-declaration)
  sort(v);  // Error: Ambiguous
}

Upvotes: 6

Related Questions