为啥结构必须与模板类在同一个命名空间中才能编译?

Posted

技术标签:

【中文标题】为啥结构必须与模板类在同一个命名空间中才能编译?【英文标题】:Why does a struct have to be in the same namespace as a template class in order to compile?为什么结构必须与模板类在同一个命名空间中才能编译? 【发布时间】:2014-12-30 19:24:53 【问题描述】:

问题的标题并没有透露太多关于我的问题,但我试图用一个短语来解释这个问题。这是问题所在,我在使用 Windows 中的 MinGW 和 Linux 中的 GCC 编译的应用程序中具有类似的代码结构。 Visual Studio 没有任何问题。结构如下:

#include <iostream>

namespace idb

    class Wrapper
    
    public:
        template<typename S>
        void boo(S& s)
        
            bind(s);
        
    ;


namespace idb // <- if this namespace changes, code explodes

    struct Fulalas
    
        int x;
    ;


namespace idb

    void bind(idb::Fulalas f)
    
        std::cout << f.x << std::endl;
    


namespace app

    class Foo
    
    public:
        void func()
        
            idb::Fulalas f;
            f.x = 5;
            w.boo(f);
        

    private:
        idb::Wrapper w;
    ;


int main()

    app::Foo f;
    f.func();
    return 0;

问题是为什么在 GCC/MinGW 中将 idb::Fulalas 更改为 aaa::Fulalas(或任何所需的名称)会产生以下错误:

..\namespace\main.cpp: In instantiation of 'void idb::Wrapper::boo(S&) [with S = aaa::Fulalas]':
..\namespace\main.cpp:41:11:   required from here
..\namespace\main.cpp:11:10: error: 'bind' was not declared in this scope, and no declarations were found by argument-dependent lookup at the point of instantiation [-fpermissive]
    bind(s);
          ^
..\namespace\main.cpp:26:7: note: 'void idb::bind(aaa::Fulalas)' declared here, later in the translation unit
  void bind(aaa::Fulalas f)

【问题讨论】:

这不是您的代码,也不是产生此错误消息的代码。请复制并粘贴一个有代表性的测试用例。 如果不想利用ADL,可以将aaa::Fulalasidb::bind的声明移到调用idb::bind之前的地方 这个问题是关于C++的,所以应该去掉C标签 @Columbo “这不是你的代码”是什么意思?当然是我的代码,我写的。 【参考方案1】:

这与结构无关。

这是关于 bind 和参数依赖查找。

What is "Argument-Dependent Lookup" (aka ADL, or "Koenig Lookup")? http://en.cppreference.com/w/cpp/language/adl

bind 使用不合格,因此在相关的命名空间中寻找它。如果 struct 与绑定重载不在同一个命名空间中,则不会看到/考虑它。

【讨论】:

你回答了一个与 Boost 无关的问题?!​​ 这与 boost 无关。 Bind 只是我给出的一个示例函数名称。 非常好的和简单的答案@sehe。你知道为什么这在 Visual Studio 2013 中可以正常工作吗? @McLeary 是的。 MSVC 是一个在两阶段查找实现方面出了名的损坏编译器(这意味着在模板附近的任何地方,这是......总是,你可以依赖名称查找是不稳定的)。大多数情况下,根据标准,您获得的查找候选者比“合法”应有的“更多”,因此这通常不是问题,但您可能会遇到可移植性问题。 @sehe:我个人认为 MSVC 的模型比 2-phase 更有意义,所以我不会将其描述为“古怪”,但肯定是不合格的。【参考方案2】:

在表达式中

bind(s)

名称bind 是依赖的,因为参数s 的类型为S,它是一个模板参数。从属名称在模板实例化时绑定。当您调用时会发生这种情况

w.boo(f);

并因此将boo 实例化为S = idb::Fulalas 类型。解析依赖名称时,会考虑来自两个来源的声明:

在模板定义处可见的声明。 来自与函数参数类型关联的命名空间的声明 实例化上下文 (14.6.4.1) 和定义上下文。

([temp.dep.res])

因此,如果在模板的定义点之后但在实例化点之前声明了一个名称,则只能通过 ADL 找到它,而不能通过普通的非限定名称查找。普通的非限定查找只能找到在模板定义点可见的名称,而不是在实例化点。因此,如果Fulalas 没有声明在与bind 相同的命名空间中,ADL 将找不到idb::bind 的声明,名称查找将完全失败。

【讨论】:

以上是关于为啥结构必须与模板类在同一个命名空间中才能编译?的主要内容,如果未能解决你的问题,请参考以下文章

包含命名空间的类模板的转发声明会导致编译错误

VS2013 遇到基类在未命名命名空间中的问题

为啥我不能使用双冒号在命名空间中前向声明一个类?

devc++格式为啥那么丑

命名空间

thinkphp5.0 中使用第三方无命名空间的类库