My confusion comes from "C++ Primer 5th edition" section 13.3, page 518.
Very careful readers may wonder why the using
declaration inside swap
does not hide the declarations for the HasPtr
version of swap
.
I tried to read its reference but still did not understand why. Could anyone explain it a little bit please? Thanks. Here is the code sample of the question.
Assume class Foo
has a member named h
, which has type HasPtr
.
void swap(HasPtr &lhs, HasPtr &rhs)
{...}
void swap(Foo &lhs, Foo &rhs)
{
using std::swap;
swap(lhs.h, rhs.h);
}
Why swap
for HasPtr
is not hidden which seems to be declared in outer scope while using std::swap
is in the inner scope? Thanks.
Because using std::swap;
does not mean "henceforth, every 'swap' should use std::swap
", but "bring all overloads of swap
from std
into the current scope".
In this case, the effect is the same as if you had written using namespace std;
inside the function.
The using-declaration using std::swap
does not hide the functions you have declared to swap HasPtr
and Foo
. It brings the name swap
form the std
namespace to the declarative region. With that, std::swap
can take part in overload resolution.
From the C++11 Standard:
7.3.3 The using declaration
1 A using-declaration introduces a name into the declarative region in which the using-declaration appears.
using-declaration:
using typename
opt nested-name-specifier unqualified-id ;
using ::
unqualified-id ;
The member name specified in a using-declaration is declared in the declarative region in which the using-declaration appears. [ Note: Only the specified name is so declared; specifying an enumeration name in a using-declaration does not declare its enumerators in the using-declaration’s declarative region. —end note ] If a using-declaration names a constructor (3.4.3.1), it implicitly declares a set of constructors in the class in which the using-declaration appears (12.9); otherwise the name specified in a using-declaration is a synonym for the name of some entity declared elsewhere.
In your case, you have:
void swap(Foo &lhs, Foo &rhs)
{
using std::swap;
swap(lhs.h, rhs.h);
}
The using std::swap;
declaration introduces the name swap
from the std
namespace into the declarative region, which is the body of the swap(Foo&, Foo&)
function. The name swap
from the global namespace is still accessible in the body of the function.
If what you posted is the entirety of the function, then you don't need the using std::swap
declaration. You could get by with just:
void swap(Foo &lhs, Foo &rhs)
{
swap(lhs.h, rhs.h);
}
since swap(HasPtr &lhs, HasPtr &rhs)
is visible from the function.
Now, take a look at the example below.
struct Bar {};
void swap(Bar& lhs, Bar& rhs)
{
}
struct Foo
{
int a;
Bar b;
};
void swap(Foo& lhs, Foo& rhs)
{
swap(lhs.a, rhs.a); // A problem
swap(lhs.b, rhs.b);
}
The line marked A problem
is a problem since there is no function named swap
that can work with two objects of typeint&
as argument. You can fix that using one of the following methods:
Use std::swap
explicitly.
void swap(Foo& lhs, Foo& rhs)
{
std::swap(lhs.a, rhs.a);
swap(lhs.b, rhs.b);
}
Introduce std::swap
in the function.
void swap(Foo& lhs, Foo& rhs)
{
using std::swap;
swap(lhs.a, rhs.a);
swap(lhs.b, rhs.b);
}
Effective C++ Third Edition by Scott Meyers (Item 24)
When compilers see the call to swap, they search for the right swap to
invoke. C++’s name lookup rules ensure that this will find any
T-specific swap at global scope or in the same namespace as the type
T.
In this case and in the second block of code, compilers look for a HasPtr swap and if they don't find it they fall back on the general version in std.
The using declaration means to consider all of the std::swap overloads as if they were defined in the same namespace as the function. Since the using declaration appears in a function body, this only has an effect temporarily: within that scope.
It is identical in effect to the following:
void swap(HasPtr &lhs, HasPtr &rhs)
{...}
void swap(int &lhs, int &rhs) { std::swap(lhs, rhs); }
void swap(char &lhs, char &rhs) { std::swap(lhs, rhs); }
// etc. for other std::swap overloads of common types
void swap(Foo &lhs, Foo &rhs)
{
swap(lhs.h, rhs.h);
// the other overloads of swap go out of scope here.
}
The regular rules of overloading ensure that the first swap is the one that gets called. This is in contrast to declaring a local variable named "swap", which would hide the first overload.
Actually, The swap
for HasPtr is hidden while using std::swap
in the inner scope, but in this case the std::swap
in the inner scope is the same as the swap
for HasPtr in the outer scope.