c++overloadingsfinaeoverload-resolutionargument-dependent-lookup

Why is this function call didn't reject the unsuitable overload?


Consider the following code:

#include<vector>
#include<ranges>
#include<algorithm>
//using namespace std;
using namespace std::ranges;
int main()
{
    std::vector<int> a = {};
    sort(a);
    return 0;
}

It's running properly.

Obviously, it called this overload function(functor, strictly speaking):

template<random_access_range _Range,
     typename _Comp = ranges::less, typename _Proj = identity>
  requires sortable<iterator_t<_Range>, _Comp, _Proj>
  constexpr borrowed_iterator_t<_Range>
  operator()(_Range&& __r, _Comp __comp = {}, _Proj __proj = {}) const
  {
return (*this)(ranges::begin(__r), ranges::end(__r),
           std::move(__comp), std::move(__proj));
  }

But after we introducing the namespace std, the function call became ambiguous(got compilation errors):

#include<vector>
#include<ranges>
#include<algorithm>
using namespace std;
using namespace std::ranges;
int main()
{
    std::vector<int> a = {};
    sort(a);
    return 0;
}

In addition to the previous reloads

 2045 |   inline constexpr __sort_fn sort{};

, there are many other overload functions found in namespace std like:

template<class _ExecutionPolicy, class _RandomAccessIterator> __pstl::__internal::__enable_if_execution_policy<_ExecutionPolicy, void> std::sort(_ExecutionPolicy&&, _RandomAccessIterator, _RandomAccessIterator)

and

template<class _RAIter, class _Compare> constexpr void std::sort(_RAIter, _RAIter, _Compare)

So my questions are:

  1. The call of sort functor in std::ranges is the best match function, isn't it? Why do these functions later found in std cause ambiguity, rather than being abandoned due to the SFINAE principle?
  2. If the functions in std are visible for sort(a) after we introduced the namespace std, shouldn't it be equally visible in the first code according to the ADL of a?

Solution

  • The problem is that std::ranges::sort is implemented as function object and not a function. From name lookup rules:

    For function and function template names, name lookup can associate multiple declarations with the same name, and may obtain additional declarations from argument-dependent lookup. [...]

    For all other names (variables, namespaces, classes, etc), name lookup must produce a single declaration in order for the program to compile.

    std::ranges::sort is a variable, and thus name lookup fails (because there is more than one declaration matching name sort). And std::ranges algorithms are explicitly allowed to be implemented as function objects (quote from std::ranges::sort cppreference):

    The function-like entities described on this page are niebloids, that is: [...]

    In practice, they may be implemented as function objects, or with special compiler extensions.

    So, as long as standard library implements std::ranges::sort as function object (and both libstdc++ and libc++ seem to do that), there is no way to make sort name lookup work if you introduce both std::ranges and std in global namespace.