【问题标题】:What is the scope of a namespace identifier?命名空间标识符的范围是什么?
【发布时间】:2015-12-13 04:26:01
【问题描述】:

对于命名空间标识符声明点声明区域范围似乎没有明确的定义/strong>,命名空间内的标识符除外——根据标准(§3.3.6/1)。

命名空间定义的声明区域是它的命名空间主体。潜在范围表示为 original-namespace-name 是由每个 命名空间定义在同一个...

虽然标准确实谈到了声明——namespace-definition是声明,但这不适用于namespace-definition的情况,因为它没有declarator,也不是 initializer——根据标准(§3.3.2/1)。

名称的声明点紧接在其完整声明符之后(第 8 条)和 其之前 初始化器(如果有的话),除非下面提到...

那么,如何确定命名空间标识符的那些?

【问题讨论】:

  • 我想知道您担心什么问题。你能举一个重要的例子吗?
  • @5gon12eder 我只想说清楚。就是这样。
  • @n.m.那也是:D 什么是命名空间的声明符?我知道有声明,但命名空间声明可能会被拆分为 (7.3\1)... 可怜的编译器编写者。
  • @EugeneZavidovsky “声明性区域”不是问题。它始终是一个封闭的命名空间。 “范围”是个问题。范围从声明点开始,没有为命名空间名称定义。
  • (...继续) 这很神奇,因为它不存在但它就在那里;就像为你打开的自动门一样。

标签: c++ language-lawyer


【解决方案1】:

根据您从标准中引用的文字,我的解释是您已经回答了自己的问题。

正如你所说,一个命名空间不能有一个完整的声明器,因为可以在任何编译单元(即源文件或源文件包含的头文件)中为任何命名空间X@987654322 创建一个额外的声明区域@。

因为永远不可能有一个完整的命名空间声明,所以永远不可能有一个命名空间的声明点。由于没有声明点,所以没有命名空间标识符之类的东西,也没有一个范围之类的东西。

这意味着命名空间只是一个标签,可以作为标识符的一部分。 istream 是命名空间 std 内的标识符,该标识符的完整名称(从命名空间 std 的声明性区域外的代码中引用)是 std::istreamusing namespace std; 所做的所有事情是,当试图找到与潜在标识符 foo 的匹配项时,告诉编译器在命名空间 std(或它具有可见性的声明性区域)中查找名为 foo 的标识符,其中将是一个候选匹配。 [这就是为什么多个命名空间的using namespace 会导致歧义,如果多个命名空间包含相同的标识符]。

【讨论】:

  • "std 不是标识符" 当然是;请参阅 7.3.1 和 7.3.2 中的制作。
  • 定义、声明和它的标识符是一回事,就像它的声明区域和范围是一回事,可以跨越多个翻译单元。
  • 您是说 std 不是标识符。那么编译器如何在查找表中使用它的分辨率或可见性呢?你也不能这样做namespace myNamespace = std { .... } 吗?
  • 好的;删除文本“std 不是标识符。
  • 没问题,伊尔修布。我的帖子中唯一的参考是你的。其余的是我的解释,不需要参考,但人们可以选择提出异议。
【解决方案2】:

根据您最初的假设:

似乎没有明确的定义 声明、声明区域、命名空间标识符的范围、 除了命名空间内的标识符——根据 标准(§3.3.6/1)。

我是从open-std.org/pdf 的标准中阅读的

3.3.6 命名空间范围 [basic.scope.namespace] 1 命名空间定义的声明区域是它的命名空间主体。申报的实体 命名空间主体被称为命名空间的成员,并且名称 由这些声明引入 命名空间被称为命名空间的成员名称。命名空间 成员名称具有命名空间范围。它的潜在范围包括 从名称的声明点(3.3.2)开始的命名空间;和 对于每个指定成员的使用指令(7.3.4) 命名空间,成员的潜在范围包括 跟随成员点的使用指令的潜在范围 的声明。 [示例:

namespace N { 
    int i; 
    int g(int a) { return a; } 
    int j(); 
    void q(); 
} 

namespace { int l=1; } 
// the potential scope of l is from its point of declaration 
// to the end of the translation unit 

namespace N { 
    int g(char a) { // overloads N::g(int)
        return l+a; // l is from unnamed namespace 
    } 

    int i; // error: duplicate definition 
    int j(); // OK: duplicate function declaration

    int j() { // OK: definition of N::j() 
        return g(i); // calls N::g(int)
    } 
    int q(); // error: different return type 
} 

——结束示例]

这使我阅读了以下部分:

3.3

3.3 范围 [basic.scope] 3.3.1 声明区域和范围 [basic.scope.declarative] 1 每个名称都在程序文本的某些部分引入,称为 声明区域,它是程序的最大部分,其中 该名称是有效的,也就是说,该名称可以用作 不合格的名称来指代同一实体。一般来说,每个 特定名称仅在某些可能不连续的范围内有效 程序文本的一部分称为其范围。确定一个范围 声明,有时方便参考潜在的 声明的范围。声明的范围与其相同 潜在范围,除非潜在范围包含另一个 同名声明。在这种情况下,潜在的范围 内部(包含)声明区域中的声明被排除在外 从外部(包含)声明的范围 声明区域。 2 [示例:在

int j = 24;
int main() {
    int i = j, j;
    j = 42;
}

标识符 j 被声明为名称两次(并使用了两次)。这 第一个 j 的声明区域包括整个示例。这 第一个 j 的潜在范围紧接在那个 j 之后开始,并且 延伸到程序的末尾,但其(实际)范围不包括 和 } 之间的文本。第二个声明区域 j 的声明(分号前的 j)包括所有 { 和 } 之间的文本,但其潜在范围不包括 i 的声明。 j的第二个声明的范围是一样的 作为其潜在范围。 —end example ] 3 a 声明的名称 声明被引入声明的范围 发生,除了朋友说明符(11.3)的存在,某些 详细类型说明符 (7.1.6.3) 和使用指令的使用 (7.3.4) 改变这种一般行为。 4 给定一组声明 单个声明性区域,每个区域指定相同的 非限定名称,(4.1)——它们都应指同一实体,或 都是指函数和函数模板;或 (4.2) — 正好是一个 声明应声明一个非类名或枚举名 typedef 名称和其他声明都应引用相同的 变量或枚举数,或都指函数和函数 模板;在这种情况下,类名或枚举名是隐藏的 (3.3.10)。 [注意:命名空间名称或类模板名称必须是 在其声明区域中是唯一的(7.3.2,第 14 条)。 ——尾注]§ 3.3.1 38

c ISO/IEC N4527 [注意:这些限制适用于声明性 引入名称的区域,不一定是 与声明发生的区域相同。尤其, 详细类型说明符 (7.1.6.3) 和友元声明 (11.3) 可以将(可能不可见的)名称引入封闭的 命名空间;这些限制适用于该地区。本地外部 声明(3.5)可以将名称引入声明区域 声明出现的地方并且还引入了一个(可能不是 可见)名称到封闭的命名空间中;这些限制适用于 两个地区。 —尾注] 5 [注:名称查找规则为 总结在 3.4 中。 ——尾注]

3.1

3.1 声明和定义 [basic.def] 1 声明(第 7 条)可以将一个或多个名称引入翻译单元或 重新声明先前声明引入的名称。如果是这样,则 声明指定了这些的解释和属性 名字。声明还可能具有以下效果: (1.1) — 静态 assertion (Clause 7), (1.2) — 控制模板实例化 (14.7.2)、(1.3) — 属性的使用(第 7 条)和 (1.4) — 无 (在空声明的情况下)。 § 3.1 33

c ISO/IEC N4527 2 声明是一个定义,除非它声明了一个 函数没有指定函数的主体(8.4),它包含 extern 说明符(7.1.1)或链接规范25(7.5)和 既不是初始化程序也不是函数体,它声明了一个静态数据 类定义(9.2、9.4)中的成员,它是类名 声明(9.1),它是一个不透明枚举声明(7.2),它是一个 模板参数(14.1),它是一个参数声明(8.3.5) 不是 a 的声明符的函数声明符 函数定义,或者它是一个 typedef 声明(7.1.3),一个 别名声明 (7.1.3)、使用声明 (7.3.3)、 static_assert-declaration(第 7 条),一个属性声明(Clause 7),一个空声明(第 7 条),一个使用指令(7.3.4),一个 显式实例化声明 (14.7.2),或显式 其声明不是定义的专业化(14.7.3)。 [ 示例:除以下一项外,其他都是定义:

int a; // defines a
extern const int c = 1; // defines c
int f(int x) { return x+a; } // defines f and defines x
struct S { int a; int b; }; // defines S, S::a, and S::b
struct X { // defines X
    int x; // defines non-static data member x
    static int y; // declares static data member y
    X(): x(0) { } // defines a constructor of X
};
int X::y = 1; // defines X::y
enum { up, down }; // defines up and down
namespace N { int d; } // defines N and N::d
namespace N1 = N; // defines N1
X anX; // defines anX

而这些只是声明:

extern int a; // declares a
extern const int c; // declares c
int f(int); // declares f
struct S; // declares S
typedef int Int; // declares Int
extern X anotherX; // declares anotherX
using N::d; // declares d

—结束示例] 3 [ 注意:在某些情况下,C++ 实现 隐式定义默认构造函数(12.1),复制构造函数 (12.8)、移动构造函数 (12.8)、复制赋值运算符 (12.8)、移动 赋值运算符 (12.8) 或析构函数 (12.4) 成员函数。 —尾注] [示例:给定

#include <string>

struct C {
    std::string s; // std::string is the standard library class (Clause 21)
};

int main() {
    C a;
    C b = a;
    b = a;
}

实现将隐式定义函数以使 C 的定义等价于 25) 出现在大括号内 链接规范中的声明序列不影响是否 声明是一个定义。 § 3.1 34

c ISO/IEC N4527

struct C {
    std::string s;
    C() : s() { }
    C(const C& x): s(x.s) { }
    C(C&& x): s(static_cast<std::string&&>(x.s)) { }
        // : s(std::move(x.s)) { }
    C& operator=(const C& x) { s = x.s; return *this; }
    C& operator=(C&& x) { s = static_cast<std::string&&>(x.s); return *this; }
        // { s = std::move(x.s); return *this; }
    ~C() { }
};

—end example ] 4 [ 注意:类名也可以隐式声明 通过详细的类型说明符(7.1.6.3)。 ——尾注] 5 一个程序是 如果任何对象的定义给该对象一个 不完整类型(3.9)。

然后你先声明:

虽然标准确实谈到了声明的内容——a namespace-definition 是声明,不适用于 命名空间定义的情况,因为它没有声明符,也没有 初始化器——根据标准(§3.3.2/1)。

然后我读到了这个:

3.3.2 声明点 [basic.scope.pdecl] 1 名称的声明点紧跟在其完整声明符之后 (第 8 条)和它的初始化器(如果有的话)之前,除非如下所述。 [ 例子:

unsigned char x = 12;
{ unsigned char x = x; }

这里第二个 x 用它自己的(不确定的)值初始化。 —结束示例]

然后你引用这个:

名称的声明点在其完成后立即 声明符(第 8 条)和它的初始化器(如果有的话)之前,除了 如下所示...

我终于读到了:

8 声明符 [dcl.decl] 1 声明符声明一个变量, 声明中的函数或类型。初始化声明器列表 出现在声明中的是逗号分隔的序列 声明符,每个声明符都可以有一个初始化器。 init-declarator-list: init-declarator init-declarator-list , init-declarator init-declarator: 声明器 initializeropt 2 三个 简单声明的组成部分是属性(7.6), 说明符 (decl-specifier-seq; 7.1) 和声明符 (初始化声明符列表)。说明符表示类型、存储 被声明的实体的类或其他属性。这 声明符指定这些实体的名称和(可选) 用 * 等运算符修改说明符的类型(指针 to) 和 () (函数返回)。也可以指定初始值 在声明符中;初始化器在 8.5 和 12.6 中讨论。 3个 声明中的 init-declarator 被单独分析,就好像它是 在自己的声明中。100 4 声明符具有语法 declarator: ptr-declarator noptr-declarator parameters-and-qualifiers trailing-return-type ptr-declarator: noptr-declarator ptr-operator ptr-declarator noptr-declarator: declarator-id 属性说明符 seqopt noptr 声明符参数和限定符 noptr 声明符 [常量表达式选择] 属性说明符 seqopt ( ptr-declarator ) 参数和限定符:( 参数声明子句) cv-qualifier-seqopt ref-qualifieropt exception-specificationopt 属性说明符-seqopt 100) A 带有多个声明符的声明通常等同于 相应的声明序列,每个声明都带有一个声明符。 即 T D1, D2, ... Dn;通常相当于 T D1; T D2; ...吨 DN;其中 T 是一个 decl-specifier-seq 并且每个 Di 是一个 init-declarator。 当其中一个声明符引入的名称时发生异常 隐藏由 decl-specifiers 使用的类型名称,这样当相同的 decl-specifiers 在后续声明中使用,它们没有 与 struct S ... 中的含义相同; SS,T; // 声明两个 不等价于 struct S ... 的 struct S 实例; S S;小号 T; // error 当 T 为 auto (7.1.6.4) 时发生另一个异常,对于 示例:自动 i = 1,j = 2.0; // 错误:i 和 j 的推断类型 不匹配而不是 auto i = 1; // 好的:我推断有 int 类型 自动 j = 2.0; // OK: j 推断为具有 double 类型的声明符 190

c ISO/IEC N4527 尾随返回类型: -> 尾随类型说明符序列抽象声明器选择 ptr 运算符: * 属性说明符 seqopt cv 限定符 seqopt & 属性说明符 seqopt && 属性说明符 seqopt 嵌套名称说明符 * 属性说明符 seqopt cv 限定符 seqopt cv-qualifier-seq:cv-qualifier cv-qualifier-seqopt cv-qualifier:const volatile ref-qualifier: & && declarator-id: ...opt id-expression 5 尾随返回类型中的可选属性说明符序列 到指定的返回类型。尾随返回类型中的类型 ID 包括最长可能的抽象声明符序列。 [ 注意:这解决了数组和函数的模糊绑定 声明者。 [ 示例:自动 f()->int(*)[4]; // 函数返回一个 指向 int 数组 [4] 的指针 // 不是返回数组 [4] 的函数 指向 int 的指针——结束示例]——结束注释]

你终于问这个了:

那么,如何确定命名空间标识符的那些?

您是否继续阅读本节?

7.3 命名空间 [basic.namespace] 1 命名空间是一个可选命名的声明区域。命名空间的名称可用于访问 在该命名空间中声明的实体;也就是说,成员 命名空间。与其他声明性区域不同,a 的定义 命名空间可以拆分为一个或多个翻译的多个部分 单位。 2 翻译单元的最外层声明区域是 命名空间;见 3.3.6。 7.3.1 命名空间定义 [namespace.def] 1 命名空间定义的语法是 namespace-name: identifier namespace-alias 命名空间定义:命名命名空间定义 未命名命名空间定义嵌套命名空间定义 命名命名空间定义:inlineopt 命名空间 属性说明符 seqopt 标识符 { namespace-body } 未命名命名空间定义:inlineopt 命名空间 属性说明符序列选择{命名空间主体} 嵌套命名空间定义:命名空间封闭命名空间说明符 :: 标识符 { namespace-body } 封闭命名空间说明符: 标识符封闭命名空间说明符 :: 标识符 § 7.3.1 168

c ISO/IEC N4527 namespace-body: declaration-seqopt 2 Every 命名空间定义应出现在全局范围内或 命名空间范围(3.3.6)。 3 在命名命名空间定义中, 标识符是命名空间的名称。如果是标识符,当 查找(3.4.1),指的是命名空间名称(但不是 namespace-alias) 在声明区域中引入,其中 named-namespace-definition 出现,namespace-definition 扩展 先前声明的命名空间。否则,标识符为 作为命名空间名称引入声明区域,其中 出现命名命名空间定义。 4 因为一个 namespace-definition 在其 namespace-body 中包含声明和一个 namespace-definition 本身就是一个声明,它遵循 命名空间定义可以嵌套。 [ 例子:

namespace Outer {
    int i;
    namespace Inner {
        void f() { i++; } // Outer::i
        int i;
        void g() { i++; } // Inner::i
    }
}

—结束示例] 5 声明的封闭命名空间是 声明在词汇上出现,除了重新声明 其原始名称空间之外的名称空间成员(例如,定义 如 7.3.1.2 所述)。这样的重新声明具有相同的封闭 命名空间作为原始声明。 [ 例子:

namespace Q {
    namespace V {
        void f(); // enclosing namespaces are the global namespace, Q, and Q::V
        class C { void m(); };
    }
    void V::f() { // enclosing namespaces are the global namespace, Q, and Q::V
        extern void h(); // ... so this declares Q::V::h
    }
    void V::C::m() { // enclosing namespaces are the global namespace, Q, and Q::V
    }
}

—结束示例] 6 如果可选的初始内联关键字出现在 特定命名空间的命名空间定义,该命名空间是 声明为内联命名空间。 inline 关键字可用于 一个命名空间定义,仅在命名空间是 先前用于最初声明的命名空间定义 该命名空间的命名空间名称。 7 可选 命名命名空间定义中的属性说明符序列属于 被定义或扩展的命名空间。 8 个内联成员 命名空间可以在大多数方面使用,就好像它们是 封闭的命名空间。具体来说,内联命名空间及其 封闭的命名空间都被添加到关联的命名空间集中 只要其中一个是,就在参数相关查找(3.4.2)中使用,并且 隐式命名内联命名空间的 using 指令 ​​(7.3.4) 插入到封闭的命名空间中,就像未命名的命名空间一样 (7.3.1.1)。此外,内联命名空间的每个成员都可以 随后被部分专业化(14.5.5),明确 实例化 (14.7.2),或显式特化 (14.7.3),就好像它 是封闭命名空间的成员。最后查了个名字 通过 § 7.3.1 169

在封闭的命名空间中

c ISO/IEC N4527 明确资格 (3.4.3.2) 将包括成员 由 using 指令引入的内联命名空间,即使 在封闭的命名空间中有该名称的声明。 9 这些属性是可传递的:如果命名空间 N 包含内联 命名空间 M,它又包含一个内联命名空间 O,然后 O 的成员可以像 M 或 N 的成员一样使用。 N 的内联命名空间集是所有内联的传递闭包 N 中的命名空间。O 的封闭命名空间集是 由最里面的非内联命名空间组成的命名空间 一个内联命名空间 O,以及任何介入的内联 命名空间。 10 嵌套命名空间定义 封闭命名空间说明符 E、标识符 I 和命名空间主体 B 是 相当于命名空间 E { 命名空间 I { B } } [ 例子:

namespace A::B::C {
    int i;
}

上面的效果与:

namespace A {
    namespace B {
        namespace C {
            int i;
        }
    }
}

——结束示例]

我认为可以从定义 7.3 - 1 & 2 和 7.3.1 - 2,3 & 4 中找到回答您问题的重要性线。前提是您正在使用命名的命名空间并且您已经了解范围、翻译单元,声明,定义和声明空间。在声明器部分中没有提到命名空间的声明器,因为它在命名空间部分下的声明部分中列出,因为命名命名空间或其定义是它的声明,因此标识符是命名空间本身的名称或其定义 - 声明。

【讨论】:

  • 感谢您提供如此详细的答复。实际上,该标准确实指定了名称空间标识符的声明性区域。但是,我仍然看不到命名空间标识符的 范围声明点 是在哪里定义的。
  • 命名空间的范围是包含整个命名空间的声明区域。声明点应紧跟在其定义声明之后:namespace std { ... } 标识符是 std,其范围位于全局空间中,并且其中的任何声明都位于 std:: 在这种情况下的声明点,因为它是一个名称空间应该紧跟在标识符 std 之后。想想这一点,您只需使用 { code here } 就可以拥有一个单独的范围,它有自己的翻译单元,没有任何限定名称。
  • (...续) 第 3.3 节涵盖本 3.3.1 中的范围与声明性区域和范围有关,第 3.3.2 节涵盖声明点。第 3.3.3 节介绍块作用域,第 3.3.6 节介绍命名空间作用域。
  • 以上大部分都控制namespace foo { ... }大括号之间的声明区域,几乎没有任何东西控制名称foo本身。
  • @FrancisCugler 那是什么意思?并且,请限制自己引用规范的相关部分,而不是整个段落,也许也可以通过鼓励它们来实现。
【解决方案3】:

我会尝试根据对操作的误解将其分解为部分。

他说:

似乎没有明确的定义 声明、声明区域、命名空间标识符的范围

// File A.h
namespace foo {        // Resides At Global Scope
    // some code here.
    void bar();        // Resided At foo:: Scope
}

// File A.cpp
namespace foo {
    // some more code here
    void bar() { 
        std::cout << "Hell World" << std::endl; 
    }
}

这里的声明区域跨越多个文件,foo 的定义和声明是 foofoo 是它的标识符。由于这是一个命名空间,因此没有像第 8.1 节中定义的变量、函数或类型那样的声明符

声明器在 声明

而且也没有初始化器,因为它是一个命名空间。它是一个具有范围的声明性区域。

我相信操作员要求的是:

那么,如何确定命名空间标识符的那些?

命名空间标识符是命名空间的名称,也是它的定义和声明。从某种意义上说,所有 3 都是相同的,命名空间的声明区域及其范围是相同的。

不,该标准没有清楚地描绘命名空间的声明点,因为它指的是使用命名空间没有的声明器。在某些情况下,命名空间可以在定义时由另一个命名空间初始化 - 声明如下:通常是别名!

namespace someLongName{
}

namespace shortVersion = someLongName {
}

在 3.3.2.1 中找到声明点的地方,如果您继续阅读本节,它将继续前进到块作用域、函数原型作用域、函数作用域、命名空间作用域、类作用域、枚举作用域、模板参数作用域、但是,如果您在声明和定义的定义下再次参考 3.1;名称空间已定义但未声明,但在第 3.3.6 节中

命名空间定义的声明区域是它的 命名空间主体。在命名空间主体中声明的实体被称为 命名空间的成员,以及这些声明引入的名称 进入命名空间的声明区域被称为成员 命名空间的名称。命名空间成员名称具有命名空间范围。 它的潜在范围包括它的命名空间,从名称的角度来看 声明(3.3.2)起;并且对于每个使用指令(7.3.4) 指定成员的命名空间,成员的潜在范围 包括使用指令的潜在范围的那部分 在成员声明点之后。

7.3 1 & 2

7.3 命名空间 [basic.namespace] 1 命名空间是一个可选命名的声明区域。命名空间的名称可用于访问 在该命名空间中声明的实体;也就是说,成员 命名空间。与其他声明性区域不同,a 的定义 命名空间可以拆分为一个或多个翻译的多个部分 单位。 2 翻译单元的最外层声明区域是 命名空间;见 3.3.6。

7.3.1 2-4

2 每个命名空间定义都应出现在全局范围内或 命名空间范围(3.3.6)。 3 在命名命名空间定义中,标识符是 命名空间。如果标识符在查找 (3.4.1) 时指的是 命名空间名称(但不是命名空间别名) 出现命名命名空间定义的声明性区域, 命名空间定义扩展了先前声明的命名空间。 否则,标识符将作为命名空间名称引入 出现命名命名空间定义的声明性区域。 4 因为命名空间定义中包含声明 namespace-body 和 namespace-definition 本身就是一个声明,它 遵循命名空间定义可以嵌套。

在这里,即使没有明确的定义,我也会尝试建议命名空间的声明点紧跟在其标识符 - 定义 - 声明之后!

namespace /*keyword*/ std /*definition, declaration, identifier*/ (point of declaration) { /*beginning of declarative region & scope*/  
    .... // some code

} // End of declarative region or scope until another matching identifier at same level of scope is found. 
  // And this can span multiple translations units.

我希望这能消除混乱。

【讨论】:

  • 我不认为命名空间标识符的声明区域和范围是相同的......
  • @Il-seobBae 我认为是在命名空间的上下文中;可以认为是一样的。声明性区域在其标识符之后开始,而这正是作用域开始的时间。可能有一些细微的差别;但两者是相互相同的。我不知道这是否是一个适当的类比,但我认为命名空间的声明区域将是一个人的财产,其中范围将是院子,以及其中的任何结构。假设有一个栅栏决定了区域,而大门让您可以访问它的范围。
猜你喜欢
  • 2010-12-02
  • 2021-04-15
  • 1970-01-01
  • 2013-02-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-11-12
相关资源
最近更新 更多