【问题标题】:Difference between sibling namespaces in nested vs global context嵌套与全局上下文中同级命名空间之间的区别
【发布时间】:2020-06-07 12:52:36
【问题描述】:

我有一些使用嵌套命名空间编写的 C++ 库。这些库使用许多数学函数,为了便于阅读,最好在不明确指定命名空间的情况下阅读这些函数。现在,代码的简化版本如下所示。

namespace Base::Sibling1 {
  float cos(float x) { return cosf(x); }
};

namespace Base::Sibling2 {
  using namespace Sibling1;
  float f(float x) { return cos(x); }
};

我们想转而使用更扁平的命名空间,主要是为了更容易使用兄弟代码扩展库。我们曾希望像这样的简单更改会起作用:

namespace Sibling1 {
  float cos(float x) { return cosf(x); }
};

namespace Sibling2 {
  using namespace Sibling1;
  float f(float x) { return cos(x); }
};

因为 Sibling2::f() 调用 cos(x) 现在是模棱两可的,所以现在失败了。

我有两个问题

  1. 为什么现在模棱两可,而不是第一个版本?
  2. 是否可以在使用using Sibling1::cos明确列出所有函数的情况下获得我们之前的行为?

【问题讨论】:

  • 我会亲自删除 using namespace Sibling1; 并明确调用 Sibling1::cos
  • 我只能用 GCC 重现您的错误。 Clang 和 MSVC 接受您的第二个 sn-p。可能只是一个错误。编辑:Clang 10 也拒绝它。很有趣。
  • 如果您有两个 using 指令 - 即 using namespace stdusing namespace Sibling1<cmath> 被包括在内,那么 std::cos()Sibling1::cos() 都同样适用于 return cos(x) - 因此模棱两可。如果您希望Sibling2::f() 调用Sibling1::cos(),则唯一避免这种情况的方法是删除using namespace std 或显式命名您想要的函数(即return Sibling1::cos())。一旦使用了 using 指令,它的效果就无法在该编译单元中撤消(即没有类似于 cancel_previous_using namespace std 的东西)。

标签: c++ namespaces


【解决方案1】:

歧义是由于 using 指令的工作方式。

[namespace.udir](强调我的)

2 using-directive 指定提名中的名称 命名空间可以在 using 指令所在的范围内使用 出现在 using 指令之后。 在非限定名称查找期间 ([basic.lookup.unqual]),名称看起来好像它们是在 最近的封闭命名空间,其中包含 using-directive 和指定的命名空间。 [注:在这种情况下, “包含”是指“直接或间接包含”。 ——尾注]

3 using-directive 不向声明性添加任何成员 它出现的区域。 [ 例子:

namespace A {
  int i;
  namespace B {
    namespace C {
      int i;
    }
    using namespace A::B::C;
    void f1() {
      i = 5;        // OK, C​::​i visible in B and hides A​::​i
    }
  }
  namespace D {
    using namespace B;
    using namespace C;
    void f2() {
      i = 5;        // ambiguous, B​::​C​::​i or A​::​i?
    }
  }
  void f3() {
    i = 5;          // uses A​::​i
  }
}
void f4() {
  i = 5;            // error: neither i is visible
}

—结束示例]

因此,鉴于您的命名空间结构,根据我在第 2 段中加粗的部分,当您编写时

using namespace Sibling1;

有点意思

namespace /* Enclosing */ {

    using Sibling1::cos;

    namespace Sibling2 {
      float f(float x) { return cos(x); }
    };

}

我标记为封闭的命名空间是Base 或全局命名空间。 “有点”位(根据第 3 段)是它不是添加的实际声明。 IE。如果 /* Enclosing */ 中已经存在名为 cos 的内容,则不是重新声明。这是设计使然,因为使用指令可能会带入很多名称,因此当它们带来的名称未被实际使用时,它不应该导致错误。

但是,在您的情况下,使用了 中引入的名称。

/* Enclosing */Base 时,它只匹配一个声明,Sibling1 中的那个,就好像它是在Base 中声明的一样。

/* Enclosing */ 是全局命名空间时,它也与那里的实际声明相匹配,大概是math.h 引入的那个(你似乎正在使用它)。所以你得到一个模棱两可(这个名字指的是两个潜在的实体)。

因此,总体而言,拒绝此代码的编译器的行为符合预期。虽然我理解你的困境,但我认为你在这里没有真正需要解决的问题。如果客户端代码发现Base::Sibling1::cos 过于冗长,它本身可以使用using namespace Base; 形式的 using 指令。或者使用命名空间别名来缩短 namespace sib1 = Base::Sibling1;

【讨论】:

  • 这是有道理的。谢谢你的解释。
【解决方案2】:

问题无法重现,如您所描述的。此外,如果您在兄弟姐妹中使用您在Base 中介绍的相同名称,则没有理由单独进行扁平化会引入歧义。因此,根本原因可能在于您未显示的代码的某些部分。

第二个版本没有定义cosf(),所以你可能正在使用一些命名空间。如果在该名称空间中还有一个cos(),则您会在两个重载之间产生歧义。我可以通过使用命名空间std 来重现这一点:

#include <iostream>
#include <cmath>
using namespace std;
namespace Sibling1 {
  float cos(float x) { return cosf(x); }
}
namespace Sibling2 {
  using namespace Sibling1;
  float f(float x) { return cos(x); }
}

Online demo: 编译错误表明哪些是歧义背后的候选者。如果您现在将using namespace 注释掉,歧义就会消失,并且代码会编译。 Online proof

命名空间旨在避免命名冲突并控制歧义:

  • 创建命名空间并在各处系统地using namespace 违背了整个目的。
  • 另一方面,很长的using 列表很难维护,尤其是当您必须在多个同级命名空间中重复它们时。这可能是首先创建Base 和嵌套的原因。

在第一个版本中,您不显示 Base 中的内容。有可能在其中使用了一些更量身定制的 using 子句而不是完整的命名空间:如果有选择地在 Base 命名空间中注入真正需要的函数,它们在兄弟姐妹中可用,避免通过注入名称可能引入的歧义不必要的功能。

【讨论】:

    猜你喜欢
    • 2013-11-22
    • 2012-05-07
    • 2020-07-10
    • 1970-01-01
    • 2012-08-12
    • 2016-05-28
    • 2020-08-21
    • 2012-05-03
    相关资源
    最近更新 更多