Function hiding and using-declaration in C++

2020-08-11 00:15发布

问题:

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.

回答1:

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.



回答2:

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 typenameopt 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:

  1. Use std::swap explicitly.

    void swap(Foo& lhs, Foo& rhs)
    {
       std::swap(lhs.a, rhs.a);
       swap(lhs.b, rhs.b);
    }
    
  2. 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);
    }
    


回答3:

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.



回答4:

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.



回答5:

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.