即使在使用命名空间指令之后也没有模糊的引用错误

tas*_*oor 10 c++ namespaces c++11

下面的代码生成call of overloaded ‘bar()’ is ambiguous错误应该是因为我bar在全局和foo命名空间中都有一个函数,并且我调用了using namespace foo指令.

namespace foo {
    void bar() {}
}

void bar() {}

using namespace foo;

int main() {
    bar();
}
Run Code Online (Sandbox Code Playgroud)

我也期待以下代码出现同样的错误:

#include <cstdlib>
#include <iostream>

int abs(int n) {
    return n > 0 ? n : -n;
}

using namespace std;

int main() {
    int k;

    cin >> k;

    cout << abs(k) << endl;
}
Run Code Online (Sandbox Code Playgroud)

我已经定义了一个int abs(int n)cstlib中存在的函数,我调用了using namespace std指令.所以应该像第一个例子一样出现错误.但没有.

我的问题是编译器如何解决这种歧义?在这种情况下会调用哪个函数,我的还是std一个?这里有UB吗?

更新:从评论和答案看来,不同的编译器似乎表现不同.那么这个行为是未定义的还是实现定义的?

我与测试它g++ 4.8.4Ubuntu 14.04-std=c++11国旗.

[请注意,我确实理解这using namespace std很糟糕,我的abs功能并不比std一个更好甚至更糟.我的问题不同.]

Jus*_*tin 6

在C++标准部分17.6.1库内容和组织中,我们在17.6.1.2中读到:

除第18条至第30条和附件D中所述外,每个标题cname的内容应与C标准库(1.2)或C Unicode TR中指定的相应标题name.h的内容相同. ,好像通过包含.但是,在C++标准库中,声明(除了在C中定义为宏的名称除外)都在命名空间std的命名空间范围(3.3.6)内.未指定是否首先在全局命名空间范围内声明这些名称,然后通过显式using-declarations将其注入命名空间std(7.3.3).

重点补充

另外,在17.6.4.3.2中我们读到了外部链接

使用外部链接声明的标准C库中的每个名称都保留给实现,以用作名称空间std和全局名称空间中具有extern"C"链接的名称

在本节的简明英语中,类似的,C标准库名称是保留的,但C标准库名称仅在全局命名空间范围内.

GLIBCXX在这里做的是完全有效的; 它abs在全局命名空间范围内std声明并将其注入使用using-declarations.

实际上,在我的系统/ g ++ 4.8.5和6.3.0使用的标准库中(我在coliru上检查过6.3.0),<cstdlib>看起来像这样:

// <stdlib.h>:

extern int abs (int __x) __THROW __attribute__ ((__const__)) __wur;
Run Code Online (Sandbox Code Playgroud)
// <cstdlib>

#include <stdlib.h>

namespace std
{
    using ::abs;
}
Run Code Online (Sandbox Code Playgroud)

它是using ::abs使std::abs调用你的函数.

您违反了ODR,因为GLIBC是一个共享库,它还提供了一个实现int abs(int).

您没有得到abs(int)"链接器错误的多重定义"可能是编译器中的一个错误; 如果他们警告这个未定义的行为会很好.


这个例子可以复制:

main.cpp中

#include <iostream>

int myabs(int);

namespace foo {
    int myabs(int n) {
        return ::myabs(n);
    }
}

int myabs(int n) {
    std::cout << "myabs inside main.cpp\n";
    return n > 0 ? n : -n;
}

using namespace foo;

int main() {
    int k = -1;

    std::cout << foo::myabs(k) << std::endl;
}
Run Code Online (Sandbox Code Playgroud)

myabs.cpp

#include <iostream>

int myabs(int n) {
    std::cout << "myabs inside myabs.cpp\n";
    return n > 0 ? n : -n;
}
Run Code Online (Sandbox Code Playgroud)

然后在命令行上:

g++ -fPIC -c myabs.cpp
g++ -shared myabs.o -o libmyabs.so
g++ -L. main.cpp -lmyabs
Run Code Online (Sandbox Code Playgroud)

运行./a.out调用main.cpp中myabs定义的内容,而如果你在main.cpp中注释掉它,它会调用myabs.cpp中的那个myabs


如何避免这个问题

如果您避免在全局命名空间中声明函数,则应该主要避免此问题.

对于你的例子,如果我们改为写:

#include <cstdlib>
#include <iostream>

namespace {
    int abs(int n) {
        return n > 0 ? n : -n;
    }
}

using namespace std;

int main() {
    int k;

    cin >> k;

    cout << abs(k) << endl;
}
Run Code Online (Sandbox Code Playgroud)

我们得到关于呼叫不明确的预期错误警告.但是,请注意,如果标准库abs在全局命名空间中声明,则无法解决问题:

int main() {
    int k;

    cin >> k;

    cout << ::abs(k) << endl;
}
Run Code Online (Sandbox Code Playgroud)

这似乎只是调用标准库版本.当然,通过避免可以避免这个问题using namespace std


Bar*_*rry 5

问题是由于C头和C++头之间的交互,这<cstdlib>真的很复杂.在libstdc ++中,它没有实现为:

namespace std {
    int abs(int );
}
Run Code Online (Sandbox Code Playgroud)

如果是这种情况,那么您的示例程序std::abs将与您对样本程序的期望相匹配foo::bar,原因完全相同.但相反,它被声明为:

// from <stdlib.h>
extern int abs(int );

// from <cstdlib>
#include <stdlib.h>

namespace std {
    using ::abs;
}
Run Code Online (Sandbox Code Playgroud)

当您声明并定义自己的时::abs(int ),这只是对先前声明的重新声明int ::abs(int ).你没有超载任何东西 - int ::abs(int)这个翻译单元只有一个!您可以看到,如果您尝试声明类似的内容long abs(int )- 您将收到有关使用其他返回类型重新声明的错误.

这是有效的,因为::abs在C头中没有定义(否则你会在重新定义时遇到编译错误) - 你通过共享库引入了该定义.因此,您最终会遇到ODR违规,因为您在TU中定义了您在GLIBC中的定义和共享库定义,因此未定义行为.我不确定为什么链接器不能捕获它.