嵌套在函数中的函数声明的命名空间

RTe*_*ete 5 c++ namespaces forward-declaration language-lawyer

出于奇怪的原因,我想在函数作用域内声明一个函数。所以我得到以下代码:

namespace NS
{
    void foo()
    {
        void bar();
        bar();
    }
}
Run Code Online (Sandbox Code Playgroud)

在另一个编译单元中,我想定义 bar. 根据我使用的编译器,我需要将 bar 放入命名空间 NS 或全局命名空间中才能链接:

叮当声:

namespace NS
{
    void bar() {}
}
Run Code Online (Sandbox Code Playgroud)

在 MSVC 上:

void bar() {}
Run Code Online (Sandbox Code Playgroud)

如果有的话,什么是好的行为?

作为一个附带问题,为什么以下代码都无法编译(在我的 2 个编译器上):

namespace NS
{
    void foo()
    {
        void bar();
        ::bar(); // bar declared in global namespace
    }
}
Run Code Online (Sandbox Code Playgroud)

或者

namespace NS
{
    void foo()
    {
        void bar();
        ::NS::bar(); // bar declared in NS namespace
    }
}
Run Code Online (Sandbox Code Playgroud)

感谢您的帮助。

吉西尔贝

Fil*_*efp 3

:长话短说;msvc做错了,而clang是正确的。这篇文章将通过显示相关片段并使用标准引用来支持主张来解释原因。

关于第一个片段/问题


介绍

块作用域中的函数声明被认为是指最内层封闭命名空间中的实体,因此下面的bar指的是NS::bar

namespace NS {
  void foo () {
    void bar (); // (A), forward-declaration of `NS::bar`

    bar ();      // call (A)
  }
}
Run Code Online (Sandbox Code Playgroud)

这意味着您在定义bar时,必须在命名空间N中执行此操作,或者使用限定 ID来引用所述实体。

namespace NS {
  void foo () {
    void bar (); // (A), forward-declaration of `NS::bar`

    bar ();      // call (A)
  }
}
Run Code Online (Sandbox Code Playgroud)

标准怎么说?( n3337 )

3.5p6 程序及联动 [basic.link]

在块作用域中声明的函数名称和块作用域声明中声明的变量名称extern具有链接。如果存在具有相同名称和类型的链接的实体的可见声明,忽略最内层封闭命名空间范围之外声明的实体,则块范围声明声明相同的实体并接收前一个声明的链接。如果存在多个这样的匹配实体,则该程序是格式错误的。否则,如果没有找到匹配的实体,则块作用域实体接收外部链接。


结论

msvc做错了,所显示的行为clang是正确的。



关于第二个片段/问题


解释

当遇到块作用域声明时,它引用最近的封闭命名空间中的实体,但它不会该命名空间中引入此类名称。

3.5p7 程序及联动 [basic.link]

当没有发现具有链接的实体的块作用域声明引用某些其他声明时,则该实体是最内部封闭命名空间的成员。然而,这样的声明不会在其命名空间范围中引入成员名称。

请参阅下面的示例:

namespace NS {
  void foo() {
    void bar();  // (A), forward-declaration of `NS::bar`
    ::NS::bar(); //      ill-formed, there is no `::NS::bar` yet
    bar ();      //           legal, call the `bar` being forward-declared by (A)
  }
}
Run Code Online (Sandbox Code Playgroud)

上面的 (A) 指的是即将声明的::NS::bar,但由于前向声明不会使命名空间 NS具有名为bar的实体,我们只能::NS::bar通过bar来引用该实体。

void bar ();    // (A), `::bar`

namespace NS {
  void foo() {
    void bar();   // (B), forward-declaration of `NS::bar`

    ::bar();      // call (A)
    bar ();       // call (B), `NS::bar`

    ::NS::bar (); // ill-formed, `namespace NS` doesn't have an entity named bar (yet) 
  }

  void bar ();    // (C), make `NS` have an entity named bar, this is the one
                  //      being referred to by (B)

  void baz () {
    ::NS::bar (); // legal, `namespace NS` now have an entity named `bar`, as being
                  //        declared in (C)
  }
}
Run Code Online (Sandbox Code Playgroud)