using directive vs using declaration swap in C++
Asked Answered
D

3

19

Please refer to the code below:

#include <algorithm>

namespace N
{

    template <typename T>
    class C
    {
    public:
        void SwapWith(C & c)
        {
            using namespace std; // (1)
            //using std::swap;   // (2)
            swap(a, c.a);
        }
    private:
        int a;
    };

    template <typename T>
    void swap(C<T> & c1, C<T> & c2)
    {
        c1.SwapWith(c2);
    }

}

namespace std
{

    template<typename T> void swap(N::C<T> & c1, N::C<T> & c2)
    {
        c1.SwapWith(c2);
    }

}

As written above, the code doesn't compile on Visual Studio 2008/2010. The error is:

'void N::swap(N::C<T> &,N::C<T> &)' : could not deduce template argument for 'N::C<T> &' from 'int'.

However, if I comment out (1) and uncomment (2), it will compile OK. What is the difference between using namespace std and using std::swap that explains this behavior?

Descry answered 22/4, 2013 at 17:8 Comment(6)
This seems to be a scope issue. The rule is (if I'm not wrong) it will always use the most local scope first. So it will use N::swap instead of std::swap even if you have using namespace stdPawsner
Btw, the code is ill-formed, and the program has Undefined Behavior. You cannot add function template overloads to the std namespace, only specializations.Draff
Also, how is this swap suppose to work, As far as I can tell it's an infinite loop.Pawsner
possible duplicate of Exceptional C++[Bug]?Forborne
There are no non-deduceable contexts, so the error message is, at the best, misleading.Chokefull
@Andy Prowl You should make that an answer: If the code is ill-formed then what a particular compile does isn't really important.Melodymeloid
A
34

The first case is a using directive (using namespace X), and what it means is that the names from namespace X will be available for regular lookup, in the first common namespace of X and the current scope. In this case, the first common namespace ancestor of ::N and ::std is ::, so the using directive will make std::swap available only if lookup hits ::.

The problem here is that when lookup starts it will look inside the function, then inside the class, then inside N and it will find ::N::swap there. Since a potential overload is detected, regular lookup does not continue to the outer namespace ::. Because ::N::swap is a function the compiler will do ADL (Argument dependent lookup), but the set of associated namespaces for fundamental types is empty, so that won't bring any other overload. At this point lookup completes, and overload resolution starts. It will try to match the current (single) overload with the call and it will fail to find a way of converting from int to the argument ::N::C and you get the error.

On the other hand a using declaration (using std::swap) provides the declaration of the entity in the current context (in this case inside the function itself). Lookup will find std::swap immediately and stop regular lookup with ::std::swap and will use it.

Amine answered 22/4, 2013 at 18:19 Comment(0)
C
10

The obvious reason is that a using declaration and a using directive have different effects. A using declaration introduces the name immediately into the current scope, so using std::swap introduces the name into the local scope; lookup stops here, and the only symbol you find is std::swap. Also, this takes place when the template is defined, so later declarations in namespace std aren't found. In the following line, the only swap that will be considered is the one defined in <algorithm>, plus those added by ADL (thus, the one in namespace N). (But is this true with VC++? The compiler doesn't implement name lookup correctly, so who knows.)

A using directive specifies that the names will appear "as if" they were declared in the nearest namespace enclosing both the directive and the nominated namespace; in your case, global namespace. And it doesn't actually introduce the names; it simply affects name lookup. Which in the case of a dependent symbol (or always, in the case of VC++) takes place at the call site.

As for why you have this particular error message: probably more an issue with VC++, since there's certainly no non-deduceable contexts in your code. But there's no reason to expect the two variants be have the same behavior, regardless of the compiler.

Chokefull answered 22/4, 2013 at 18:17 Comment(5)
+1. Is this true in VS? The implementation is wrong in that it will also add declarations found between the template definition and the point of instantiation (same as many versions of gcc, and CC), but other than that it will find the correct one in this case (there many other cases where it won't though :)Foremast
@DavidRodríguez-dribeas MS implements a version of name lookup that was common before C++98 was adopted. They've obviously adapted it some since then, since the original version was defined before namespaces. How using namespace x; and using x; are interpreted in this version is anyone's guess. (And isn't 20 years long enough for them to have implemented the standard?)Chokefull
@JamesKanze MSVC does not implement two-phase name lookup correctly, by delaying lookup for nondependent names to the second phase. See e.g. this question That issue should not enter into play here AFAICS.Shammy
@rhalbersma MSVC does not implement two-phase lookup, period. It doesn't even attempt to. As with one or two other cases, it has decided, explicitly, to ignore the standard, and to go its own way.Chokefull
MSVC implemented two-phase name lookup in 2017 :-) (BTW, I heard that they implement it years ago, but for money, the feature is cut)Grouping
T
2

Note: I have removed your swap definition in namespace std. It is not relevant here. Even with out it the code will have the same issues.


This is due to look up rule differences between using directive(using namespace std) and the using declaration(using std::swap)

Microsoft says

If a local variable has the same name as a namespace variable, the namespace variable is hidden. It is an error to have a namespace variable with the same name as a global variable.

#include<iostream>

namespace T {
    void flunk(int) { std::cout << "T";}
}

namespace V {
    void flunk(int) { std::cout << "V";}
}


int main() {
    using T::flunk;   // makes T::flunk local
    // using V::flunk;  // makes V::flunk local. This will be an error
    using namespace V;  // V::flunk will be hidden
    flunk(1);
}

According to this, due to your

template <typename T>
void swap(C<T> & c1, C<T> & c2)

std::swap will be hidden when you use

using namespace std; 

So the only swap available for template deduction is N::swap and it won't work for ints because it expects a template class as argument.

but not when

using std::swap;

In this case it becomes equivalent to local definition. And can be used with out problem.

Techno answered 22/4, 2013 at 17:24 Comment(4)
The posted error message mentions no such ambiguity. It's clear that it's only considering one swap.Guaiacol
As long as the compiler can see the swap in <algorithm>, his code should compile. The other two would fall under the category SFINAE, and simply not be considered (in the case where they are visible).Chokefull
@JamesKanze Yes but according to the rules of lookup above there is no other swap to use for SFINAE in the first place. There is only one visible swap.Techno
@JamesKanze Remeber the compiler can not see the swap in std due to the local swap when he uses using namespace std.Techno

© 2022 - 2024 — McMap. All rights reserved.