Why Does C++ Parameter Scope Affect Function Lookup Within a Namespace

How does ADL affect this piece of C++ code?

In this case, normal name lookup finds N2::foo, and N1::foo is found by ADL, they're both added to the overload set, then overload resolution is performed and the calling is ambiguous.

BTW: Without using N2::foo; in main(), ::foo will be found by normal name lookup, and N1::foo is found by ADL too; as the result the calling is still ambiguous.

Updated:

So, the question here is why ::foo can not be called by "foo(N1::S{});" in the main function?

Because with the usage of using N2::foo;, the name N2::foo is introduced in the main function. When calling foo the name N2::foo will be found at the scope of main, then name lookup stops, the further scope (the global namespace) won't be examined, so ::foo won't be found and added to overload set at all. As the result N2::foo is called for both cases.

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.

BTW: If you put using N2::foo; in global namespace before main, foo(N1::S{}); would call ::foo. Both N2::foo and ::foo are found by name lookup and ::foo wins in overload resolution.

LIVE

Confusion around function call resolution

The code in the OP is equivalent to this:

using std::swap; // only for name lookup inside ns::swap

namespace ns {
template <typename T>
void swap(T& a, T& b) {
swap(a, b);
}
}

Why? Because using-directives like using namespace std; have a very peculiar behaviour C++14 [namespace.udir]p2:

A using-directive specifies that the names in the nominated namespace
can be used in the scope in which the using-directive appears after
the using-directive. During unqualified name lookup, the names
appear as if they were declared in the nearest enclosing namespace
which contains both the using-directive and the nominated namespace.

The nearest enclosing namespace that contains both namespace std and the block scope of function ns::swap is the global namespace.

Using-declarations such as using std::swap; on the other hand really introduce names into the scope in which they appear, not in some enclosing scope.


The lookup of a function call expression such as swap(a, b) is called unqualified lookup. The identifier swap has not been qualified with any namespace or class name, as opposed to ns::swap, which has been qualified via ns::. Unqualified lookup for potential names of functions consists of two parts: pure unqualified lookup and argument-dependent lookup.

Pure unqualified lookup stops at the nearest enclosing scope that contains the name. In the OP's example, as illustrated by the equivalent transformation shown above, the nearest scope that contains a declaration of the name swap is the namespace ns. The global scope will not be searched, std::swap will not be found via pure unqualified lookup.

Argument-dependent lookup searches all scopes (here: only namespaces and classes) associated with the argument types. For class types, the namespace in which the class has been declared in is an associated scope. Types of the C++ Standard Library such as std::vector<int> are associated with namespace std, hence std::swap can be found via argument-dependent lookup for the expression swap(a, b) if T is a C++ Standard Library type. Similarly, your own class types allow finding a swap function in the namespaces they have been declared in:

namespace N2 {
class MyClass {};
void swap(MyClass&, MyClass&);
}

Therefore, if argument-dependent lookup does not find a better match than pure unqualified lookup, you'll end up calling ns::swap recursively.


The idea behind calling swap unqualified, that is, swap(a, b) instead of std::swap(a, b) is that functions found via argument-dependent lookup are assumed to be more specialized than std::swap. Specializing a function template such as std::swap for your own class template type is impossible (since partial function template specializations are forbidden), and you may not add custom overloads to namespace std. The generic version of std::swap is implemented typically as follows:

template<typename T>
void swap(T& a, T& b)
{
T tmp( move(a) );
a = move(b);
b = move(tmp);
}

This requires a move-construction plus two move-assignments, which might even fall back to copies. Therefore, you can provide a specialized swap function for your own types in the namespaces associated with those types. Your specialized version can make use of certain properties of, or private access to, your own types.

How does the using directive affect function arguments in C++?

The code is correct but it has nothing to do with argument dependent look-up. Also, the using declaration only affects finding of usesFoo and not foo: once you have uttered the name of a class member, other names are looked up in the context of this class. Since foo is a member of test::usesFoo` it found. Without the using directive you'd need to define the member function like this:

void test::usesFoo::memberFunction(foo& input) {
(void)input;
}

The relevant clause for this is 3.4.1 Unqualified Name Look-up [basic.lookup.unqual] paragraph 6:

A name used in the definition of a function following the function’s declarator-id that is a member of namespace N (where, only for the purpose of exposition, N could represent the global scope) shall be declared before its use in the block in which it is used or in one of its enclosing blocks (6.3) or, shall be declared before its use in namespace N or, if N is a nested namespace, shall be declared before its use in one of N’s enclosing namespaces.

Argument dependent lookup only enters the picture when a function is called, not when it is defined. These things have nothing to do with each other at all.

Name Lookup and class scope

When member functions are defined in namespace scope C++ provides special name lookup rules for unqualified names that follow the function’s declarator-id (3.4.1/8). Such names are looked up in class scope before they are looked up in namespace scope.

Since the return type in an "ordinary" member function definition precedes function’s declarator-id, the aforementioned special rules do not apply to it. It is looked up in accordance with the "usual" rules: in namespace scope.

For this reason your function definition's return type refers to ::Type, not to Exercise::Type. It does not match any of the declarations made inside the class. The code is ill-formed.

If you want the unqualified return type name to be looked up in class scope as well, use the new trailing return type syntax in function declaration, since in this syntax the return type follows function’s declarator-id

auto Exercise::setVal(Type parm) -> Type {  
val = parm + initVal();
return val;
}

What are the pitfalls of ADL?

There is a huge problem with argument-dependent lookup. Consider, for example, the following utility:

#include <iostream>

namespace utility
{
template <typename T>
void print(T x)
{
std::cout << x << std::endl;
}

template <typename T>
void print_n(T x, unsigned n)
{
for (unsigned i = 0; i < n; ++i)
print(x);
}
}

It's simple enough, right? We can call print_n() and pass it any object and it will call print to print the object n times.

Actually, it turns out that if we only look at this code, we have absolutely no idea what function will be called by print_n. It might be the print function template given here, but it might not be. Why? Argument-dependent lookup.

As an example, let's say you have written a class to represent a unicorn. For some reason, you've also defined a function named print (what a coincidence!) that just causes the program to crash by writing to a dereferenced null pointer (who knows why you did this; that's not important):

namespace my_stuff
{
struct unicorn { /* unicorn stuff goes here */ };

std::ostream& operator<<(std::ostream& os, unicorn x) { return os; }

// Don't ever call this! It just crashes! I don't know why I wrote it!
void print(unicorn) { *(int*)0 = 42; }
}

Next, you write a little program that creates a unicorn and prints it four times:

int main()
{
my_stuff::unicorn x;
utility::print_n(x, 4);
}

You compile this program, run it, and... it crashes. "What?! No way," you say: "I just called print_n, which calls the print function to print the unicorn four times!" Yes, that's true, but it hasn't called the print function you expected it to call. It's called my_stuff::print.

Why is my_stuff::print selected? During name lookup, the compiler sees that the argument to the call to print is of type unicorn, which is a class type that is declared in the namespace my_stuff.

Because of argument-dependent lookup, the compiler includes this namespace in its search for candidate functions named print. It finds my_stuff::print, which is then selected as the best viable candidate during overload resolution: no conversion is required to call either of the candidate print functions and nontemplate functions are preferred to function templates, so the nontemplate function my_stuff::print is the best match.

(If you don't believe this, you can compile the code in this question as-is and see ADL in action.)

Yes, argument-dependent lookup is an important feature of C++. It is essentially required to achieve the desired behavior of some language features like overloaded operators (consider the streams library). That said, it's also very, very flawed and can lead to really ugly problems. There have been several proposals to fix argument-dependent lookup, but none of them have been accepted by the C++ standards committee.



Related Topics



Leave a reply



Submit