Namespaces have two impacts on function matching (§ 6.4, p. 233). One of these should be obvious: A using
declaration or directive can add functions to the candidate set. The other is much more subtle.
As we saw in the previous section, name lookup for functions that have class-type arguments includes the namespace in which each argument’s class is defined. This rule also impacts how we determine the candidate set. Each namespace that defines a class used as an argument (and those that define its base classes) is searched for candidate functions. Any functions in those namespaces that have the same name as the called function are added to the candidate set. These functions are added even though they otherwise are not visible at the point of the call:
namespace NS {
class Quote { /* ... */ };
void display(const Quote&) { /* ... */ }
}
// Bulk_item's base class is declared in namespace NS
class Bulk_item : public NS::Quote { /* ... */ };
int main() {
Bulk_item book1;
display(book1);
return 0;
}
The argument we passed to display
has class type Bulk_item
. The candidate functions for the call to display
are not only the functions with declarations that are in scope where display
is called, but also the functions in the namespace where Bulk_item
and its base class, Quote
, are declared. The function display(const Quote&)
declared in namespace NS
is added to the set of candidate functions.
using
DeclarationsTo understand the interaction between using
declarations and overloading, it is important to remember that a using
declaration declares a name, not a specific function (§ 15.6, p. 621):
using NS::print(int); // error: cannot specify a parameter list
using NS::print; // ok: using declarations specify names only
When we write a using
declaration for a function, all the versions of that function are brought into the current scope.
A using
declaration incorporates all versions to ensure that the interface of the namespace is not violated. The author of a library provided different functions for a reason. Allowing users to selectively ignore some but not all of the functions from a set of overloaded functions could lead to surprising program behavior.
The functions introduced by a using
declaration overload any other declarations of the functions with the same name already present in the scope where the using
declaration appears. If the using
declaration appears in a local scope, these names hide existing declarations for that name in the outer scope. If the using
declaration introduces a function in a scope that already has a function of the same name with the same parameter list, then the using
declaration is in error. Otherwise, the using
declaration defines additional overloaded instances of the given name. The effect is to increase the set of candidate functions.
using
DirectivesA using
directive lifts the namespace members into the enclosing scope. If a namespace function has the same name as a function declared in the scope at which the namespace is placed, then the namespace member is added to the overload set:
namespace libs_R_us {
extern void print(int);
extern void print(double);
}
// ordinary declaration
void print(const std::string &);
// this using directive adds names to the candidate set for calls to print:
using namespace libs_R_us;
// the candidates for calls to print at this point in the program are:
// print(int) from libs_R_us
// print(double) from libs_R_us
// print(const std::string &) declared explicitly
void fooBar(int ival)
{
print("Value: "); // calls global print(const string &)
print(ival); // calls libs_R_us::print(int)
}
Differently from how using
declarations work, it is not an error if a using
directive introduces a function that has the same parameters as an existing function. As with other conflicts generated by using
directives, there is no problem unless we try to call the function without specifying whether we want the one from the namespace or from the current scope.
using
DirectivesIf many using
directives are present, then the names from each namespace become part of the candidate set:
namespace AW {
int print(int);
}
namespace Primer {
double print(double);
}
// using directives create an overload set of functions from different namespaces
using namespace AW;
using namespace Primer;
long double print(long double);
int main() {
print(1); // calls AW::print(int)
print(3.1); // calls Primer::print(double)
return 0;
}
The overload set for the function print
in global scope contains the functions print(int), print(double)
, and print(long double)
. These functions are all part of the overload set considered for the function calls in main
, even though these functions were originally declared in different namespace scopes.
Exercise 18.20: In the following code, determine which function, if any, matches the call to compute
. List the candidate and viable functions. What type conversions, if any, are applied to the argument to match the parameter in each viable function?
namespace primerLib {
void compute();
void compute(const void *);
}
using primerLib::compute;
void compute(int);
void compute(double, double = 3.4);
void compute(char*, char* = 0);
void f()
{
compute(0);
}
What would happen if the using
declaration were located in main
before the call to compute?
Answer the same questions as before.