【问题标题】:using directive vs using declaration swap in C++在 C++ 中使用指令与使用声明交换
【发布时间】:2013-04-15 16:30:33
【问题描述】:

请参考以下代码:

#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),它将编译 OK。 using namespace stdusing std::swap 之间有什么区别可以解释这种行为?

【问题讨论】:

  • 这似乎是一个范围问题。规则是(如果我没记错的话)它总是首先使用最本地的范围。所以它会使用N::swap而不是std::swap,即使你有using namespace std
  • 顺便说一句,代码格式不正确,并且程序具有未定义的行为。您不能将函数模板 overloads 添加到 std 命名空间,只能添加特化。
  • Exceptional C++[Bug]? 的可能重复项
  • 没有不可推断的上下文,因此错误消息充其量是误导性的。
  • @Andy Prowl 你应该这样回答:如果代码格式不正确,那么特定编译的作用并不重要。

标签: c++ swap using-directives using-declaration


【解决方案1】:

第一种情况是 using 指令 ​​(using namespace X),这意味着来自命名空间 X 的名称将可用于常规查找,在 X 的第一个公共命名空间和当前范围内。在这种情况下,::N::std 的第一个公共命名空间祖先是 ::,因此 using 指令将使 std::swap 仅在查找命中 :: 时可用。

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

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

【讨论】:

    【解决方案2】:

    显而易见的原因是 using 声明和 using 指令有不同的效果。使用声明 立即将名称引入当前范围,因此 using std::swap 将名称引入本地范围; 查找在此处停止,您找到的唯一符号是std::swap。 此外,这发生在定义模板时,所以稍后 未找到命名空间 std 中的声明。在下面的 行,only swap 会被考虑 在&lt;algorithm&gt; 中定义,加上由 ADL 添加的那些(因此, 在命名空间N)。 (但是 VC++ 是这样吗?编译器 没有正确实现名称查找,所以谁知道。)

    using 指令指定名称将显示为“好像” 它们是在最近的命名空间中声明的,包含 指令和指定的命名空间;在你的情况下,全球 命名空间。它实际上并没有介绍名称;它 只会影响名称查找。在受抚养人的情况下 符号(或者总是,在 VC++ 的情况下)发生在调用时 网站。

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

    【讨论】:

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

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


    这是由于查找using directive(using namespace std) 和using 之间的规则差异declaration(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; 
    

    因此,唯一可用于模板推导的swapN::swap,它不适用于ints,因为它需要template class 作为参数。

    不是何时

    using std::swap;
    

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

    【讨论】:

    • 发布的错误消息没有提到这种歧义。很明显,它只考虑了一个swap
    • 只要编译器能看到&lt;algorithm&gt;中的swap,他的代码就应该编译通过。另外两个属于 SFINAE 类别,根本不考虑(在它们可见的情况下)。
    • @JamesKanze 是的,但是根据上面的查找规则,首先没有其他交换可用于 SFINAE。 只有一个可见交换。
    • @JamesKanze 请记住,当他使用using namespace std 时,由于本地swap,编译器在std 中看不到swap
    猜你喜欢
    • 2018-06-29
    • 2020-09-21
    • 1970-01-01
    • 1970-01-01
    • 2019-03-10
    • 2017-04-02
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多