【问题标题】:repeated extern declarations of qualified identifiers C++合格标识符的重复外部声明 C++
【发布时间】:2015-07-30 17:07:15
【问题描述】:

为什么不能在函数内部重新声明限定名?

以下代码编译失败(在MSVC2015和clang中)

int i;

namespace N
{
int j;
}

void foo()
{
    extern int i;
    extern int i;
    extern int N::j;
    extern int N::j;
}

int main()
{
    return 0;
}

但是,如果我们将两行 extern int N::j; 移动到 void foo() 之前,那么代码编译得很好。


更新值得注意的是

  1. 不合格名称的重复声明确实有效,
  2. ::i::N::j 都已在各自的声明中定义,
  3. ::N::jfoo 不可见
  4. 语法确实允许extern int ::N::j 并且它不是一个定义
  5. 以下代码也失败了(感谢 T.C. 指出这一点)

.

int i;
void foo()
{
    extern int ::i;
}
  1. 以下代码在 MSVC 中有效,但在 clang 中发出警告

.

int i;
extern int ::i;

【问题讨论】:

标签: c++ c++11 language-lawyer


【解决方案1】:

T.C.引导我到 [dcl.meaning]p1

…当 declarator-id 被限定时,声明应引用限定符所指的类或命名空间的先前声明的成员(或者,在命名空间的情况下,内联命名空间集合的元素)该命名空间 (7.3.1)) 或其专业化......

在我看来,这是标准没有被实现忠实地代表的情况。

【讨论】:

  • 特别是,在 CWG482 之前,不允许在声明中使用限定名称,只有少数与此处无关的例外情况。 CWG482 的决议放宽了限制,但编译器似乎尚未更新以实施更改。
【解决方案2】:

这里是命名空间成员定义与声明的问题。请参阅 C++11 规范:

7.3.1.2 命名空间成员定义 [namespace.memdef]

1 成员(包括模板的明确特化 (14.7.3)) 命名空间可以在该命名空间中定义。 [ 示例:命名空间 X { void f() { / ... / } } —结束示例]

2 名成员 命名空间也可以通过显式定义在该命名空间之外 正在定义的名称的限定 (3.4.3.2),前提是 正在定义的实体已经在命名空间中声明,并且 定义出现在命名空间中的声明点之后 包含声明的命名空间。

因此,下面的修改后的代码可以编译。

int i;

namespace N
{
    int j;

    void foo()
    {
        extern int i;
        extern int j;
    }
}

void foo()
{
    extern int i;

    using namespace N;
    extern int j;
}

int main()
{
    return 0;
}

下面是使用externnamespace 的通用示例: 在一个 CPP 文件中:

namespace N
{
    int j;
}

在其头文件中:

namespace N
{
    extern int j;
}

更新:

更多关于外部、命名空间、声明和定义

:: 范围解析运算符不能用于声明。它可以用于定义。将 :: 用于 extern 声明将是不正确的。 请参阅 C++11 规范:

7.5 联动规范 [dcl.link] ... ...
4 联动规格嵌套。当联动规格嵌套时,最里面 一是确定语言联系。 链接规范不 建立一个范围。链接规范应仅出现在 命名空间范围 (3.3)。 在链接规范中,指定的 语言链接适用于所有函数的函数类型 声明符、带有外部链接的函数名和变量名 在链接规范中声明了外部链接。

3.3.6 命名空间范围 [basic.scope.namespace]

1 命名空间定义的声明区域是它的命名空间主体。 …

// Compiled with VC2013.
// extern int ::i;   // error C2039: 'i' : is not a member of '`global namespace''
extern int i;     // declaration  
int i;  // definition

namespace N
{
    extern int j;  // declaration
}
int N::j;  // definition
namespace N
{
     // int j;  // definition
}

void foo()
{
    // extern int ::i;  // fatal error C1506: unrecoverable block scoping error
    extern int i;     // declaration  
    // extern int N::j;  // error C2086: 'int N::j' : redefinition
}

int main()
{
    return 0;
}

【讨论】:

  • 您引用的规则适用于定义,但 extern 声明不是定义 [basic.def]/2:“声明是定义,除非 [...] 它包含 extern 说明符并且两者都不包含初始化器也不是……”
  • 添加了命名空间范围声明的规则。请参阅“有关外部、命名空间、声明和定义的更多信息”部分。
  • 如果你想称某事为“未定义的行为”,你需要比声明区域上不相关的标准引用和 ipse dixit 更强大的东西,特别是因为这是不是明显无法诊断的东西。
  • 我同意只能在该命名空间内将名称引入命名空间。但是,extern int ::i 不会将i 引入全局空间。它会将名称 i 引入当前空间,以便它引用全局 i,不是吗?
  • 请参阅 C++11 标准:7.5 链接规范 [dcl.link] ... ... 4 链接规范嵌套。当链接规范嵌套时,最里面的一个确定语言链接。链接规范不建立范围。链接规范应仅出现在命名空间范围 (3.3) 中。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-11-13
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多