使用指令与在 C++ 中使用声明交换

using directive vs using declaration swap in C++

提问人:ilovekonoka 提问时间:4/23/2013 最后编辑:curiousguyilovekonoka 更新时间:1/24/2017 访问量:12215

问:

请参考以下代码:

#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);
    }

}

如上所述,代码不会在 Visual Studio 2008/2010 上编译。错误是:

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

但是,如果我注释掉 (1) 并取消注释 (2),它将编译正常。和 之间有什么区别可以解释这种行为?using namespace stdusing std::swap

C++ 交换 using 指令 using-declaration

评论

4赞 andre 4/23/2013
这似乎是一个范围问题。规则是(如果我没记错的话)它将始终首先使用最本地的范围。所以它会使用而不是即使你有N::swapstd::swapusing namespace std
10赞 Andy Prowl 4/23/2013
顺便说一句,代码格式不正确,并且程序具有未定义的行为。不能将函数模板重载添加到命名空间,只能将专用化添加到命名空间。std
2赞 CB Bailey 4/23/2013
异常C++[错误]的可能副本?
1赞 James Kanze 4/23/2013
没有不可推导的上下文,因此错误消息充其量是误导性的。
1赞 Mark B 4/23/2013
@Andy 徘徊 你应该把它作为一个答案:如果代码的格式不正确,那么特定编译的作用并不重要。

答:

2赞 stardust 4/23/2013 #1

注意我已经删除了命名空间 std 中的交换定义。这里无关紧要。即使没有它,代码也会有同样的问题。


这是由于查找 () 和声明之间的规则差异using directiveusing namespace stdusing(using std::swap)

Microsoft

如果是局部变量 与命名空间变量同名,命名空间变量为 隐藏。具有相同名称的命名空间变量是错误的 作为全局变量。

#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);
}

据此,由于您的

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

std::swap使用时会隐藏

using namespace std; 

因此,唯一可用于模板推导的是,它不适用于 s,因为它需要 as 参数。swapN::swapinttemplate class

但不是什么时候

using std::swap;

在这种情况下,它等同于本地定义。并且可以毫无问题地使用。

评论

1赞 Mooing Duck 4/23/2013
发布的错误消息没有提到这种歧义。很明显,它只考虑一个.swap
1赞 James Kanze 4/23/2013
只要编译器能看到 in ,他的代码就应该编译。另外两个将属于 SFINAE 类别,根本不被考虑(在它们可见的情况下)。swap<algorithm>
1赞 stardust 4/23/2013
@JamesKanze 是的,但根据上面的查找规则,首先没有其他交换可用于 SFINAE。只有一个可见的交换。
1赞 stardust 4/23/2013
@JamesKanze Remeber 编译器在使用 .swapstdswapusing namespace std
10赞 James Kanze 4/23/2013 #2

显而易见的原因是 using 声明和 using 指令具有不同的效果。using 声明 立即将名称引入当前作用域,因此将名称引入本地作用域; 查找到此为止,您找到的唯一符号是 。 此外,这发生在定义模板时,所以稍后 找不到命名空间中的声明。在下文中 行,唯一要考虑的就是那条 在 中定义,加上 ADL 添加的那些(因此,一个 在命名空间中)。(但 VC++ 是这样吗?编译器 没有正确实现名称查找,所以谁知道呢。using std::swapstd::swapstdswap<algorithm>N

using 指令指定名称将“好像”显示 它们在最近的命名空间中声明,将 指令和指定的命名空间;就您而言,全局 命名空间。它实际上并没有介绍这些名字;它 只是影响名称查找。在受抚养人的情况下 符号(或始终,在 VC++ 的情况下)发生在调用时 网站。

至于为什么你有这个特定的错误消息:可能更多 VC++ 的问题,因为肯定没有不可推导的 代码中的上下文。但没有理由期待两者 无论编译器如何,变体都具有相同的行为。

评论

1赞 David Rodríguez - dribeas 4/23/2013
+1.在 VS 中是这样吗?该实现是错误的,因为它还将添加在模板定义和实例化点之间找到的声明(与许多版本的 gcc 和 CC 相同),但除此之外,它会在这种情况下找到正确的声明(在许多其他情况下它不会:)
1赞 James Kanze 4/23/2013
@DavidRodríguez-dribeas MS 实现了在采用 C++98 之前很常见的名称查找版本。从那时起,他们显然已经对其进行了一些调整,因为原始版本是在命名空间之前定义的。在这个版本中如何解释和解释是任何人的猜测。(难道20年的时间还不够他们实施该标准吗?using namespace x;using x;
1赞 TemplateRex 4/23/2013
@JamesKanze MSVC 无法正确实现两阶段名称查找,方法是将非依赖名称的查找延迟到第二阶段。例如,参见这个问题:这个问题不应该在这里发挥作用,AFAICS。
1赞 James Kanze 4/23/2013
@rhalbersma MSVC 不实现两阶段查找,周期。它甚至没有尝试这样做。与其他一两个案例一样,它明确决定无视该标准,走自己的路。
0赞 Chen Li 6/12/2018
MSVC 在 2017 年实施了两阶段名称查找:-)(顺便说一句,我听说他们几年前就实现了,但为了钱,该功能被削减了)
33赞 David Rodríguez - dribeas 4/23/2013 #3

第一种情况是 using 指令 (),它的意思是命名空间中的名称将可用于在 和 当前范围的第一个公共命名空间中进行常规查找。在本例中,和 的第一个公共命名空间祖先是 ,因此 using 指令仅在查找命中 时才可用。using namespace XXX::N::std::std::swap::

这里的问题是,当查找开始时,它会在函数内部查看,然后在类内部,然后在内部找到。由于检测到潜在的过载,因此常规查找不会继续到外部命名空间。因为是一个函数,编译器将执行 ADL(参数相关查找),但基本类型的关联命名空间集为空,因此不会带来任何其他重载。此时,查找完成,并开始解决过载问题。它将尝试将当前(单个)重载与调用匹配,并且无法找到从参数转换为参数的方法,并且您会收到错误。N::N::swap::::N::swapint::N::C

另一方面,using 声明 () 在当前上下文中提供实体的声明(在本例中为函数本身)。查找将立即找到并停止常规查找并将使用它。using std::swapstd::swap::std::swap