以下代码生成call of overloaded ‘bar()’ is ambiguous
错误应该是因为我在全局和bar
命名空间中都有一个函数foo
,我调用了using namespace foo
指令。
namespace foo {
void bar() {}
}
void bar() {}
using namespace foo;
int main() {
bar();
}
我也期待以下代码出现同样的错误:
#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;
}
我定义了一个函数int abs(int n)
,就像cstlib中存在的函数一样,我调用了using namespace std
指令。所以应该像第一个例子一样出现错误。但没有。
我的问题是编译器如何解决这种歧义?在这种情况下会调用哪个函数,我或std
的函数?这里有UB吗?
更新:从评论和答案来看,不同的编译器似乎表现不同。那么这个行为是未定义的还是实现定义的?
我已使用g++ 4.8.4
在Ubuntu 14.04
上使用-std=c++11
标记对其进行了测试。
[请注意,我确实理解using namespace std
不好,我的abs
功能并不比std
更好或更差。我的问题不同。]
答案 0 :(得分:6)
在C ++标准部分17.6.1 Library contents and organization中,我们在17.6.1.2中读到:
除第18条至第30条及附件D所述外,其内容为 每个标题cname应与相应的标题相同 标题name.h,如C标准库(1.2)或C中所指定 Unicode TR,视情况而定,就像包含一样。在C中 但是,++标准库,声明(在C中定义为宏的名称除外)在命名空间范围(3.3.6)内 命名空间标准。 未指明这些名称是否是第一个 在全局命名空间范围内声明,然后注入 命名空间std由显式使用声明(7.3.3)。
强调添加
此外,我们在17.6.4.3.2 External linkage中阅读了
使用外部链接声明的标准C库中的每个名称 保留给实现用作extern“C”的名称 在命名空间std和全局命名空间中的链接
在本节的简明英语中,类似的,C标准库名称是保留的,但C标准库名称仅在全局命名空间范围内。
GLIBCXX在这里做的是完全有效的;它在全局命名空间范围内声明abs
并使用using-declarations将其注入std
。
的确,在我的系统/ g ++ 4.8.5和6.3.0使用的标准库中(我在coliru上检查过6.3.0),<cstdlib>
看起来像这样:
// <stdlib.h>:
extern int abs (int __x) __THROW __attribute__ ((__const__)) __wur;
// <cstdlib>
#include <stdlib.h>
namespace std
{
using ::abs;
}
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;
}
<强> myabs.cpp 强>
#include <iostream>
int myabs(int n) {
std::cout << "myabs inside myabs.cpp\n";
return n > 0 ? n : -n;
}
然后在命令行上:
g++ -fPIC -c myabs.cpp
g++ -shared myabs.o -o libmyabs.so
g++ -L. main.cpp -lmyabs
正在运行./a.out
调用 main.cpp 中定义的myabs
,而如果您在 main.cpp中注释myabs
,它从 myabs.cpp
如果您避免在全局命名空间中声明函数,则应该主要避免此问题。
对于您的示例,如果我们改为写:
#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;
}
我们收到有关呼叫不明确的预期错误警告。但是,请注意,如果标准库在全局命名空间中声明abs
,则无法解决问题:
int main() {
int k;
cin >> k;
cout << ::abs(k) << endl;
}
这似乎只是调用标准库版本。当然,避免using namespace std
答案 1 :(得分:5)
由于C头和C ++头之间的交互,问题是<cstdlib>
is really complicated。在libstdc ++中,它没有实现为:
namespace std {
int abs(int );
}
如果是这种情况,那么std::abs
的示例程序与您对foo::bar
的示例程序的期望相符,原因完全相同。但相反,它被声明为:
// from <stdlib.h>
extern int abs(int );
// from <cstdlib>
#include <stdlib.h>
namespace std {
using ::abs;
}
当您声明并定义自己的::abs(int )
时,这只是对先前声明的int ::abs(int )
的重新声明。您没有超载任何内容 - 此翻译单元中只有一个int ::abs(int)
!您可以看到,如果您尝试声明类似long abs(int )
的内容 - 您会因为使用其他返回类型重新声明而收到错误。
这是有效的,因为C头中的::abs
未定义(否则您将在重新定义时遇到编译错误) - 您通过共享库引入该定义。因此,您最终会遇到ODR违规,因为您在TU中定义了您在GLIBC中的定义和共享库定义,因此未定义行为。我不确定链接器为什么没有抓住它。
答案 2 :(得分:1)
如果以下列方式声明abs
函数:
void abs(int n) {
return n > 0 ? n : -n;
}
(返回类型已从int
更改为void
)
这会引发error: ambiguating new declaration of 'void abs(int)'
因为在stdlib
中它声明为int abs(int n)
,但我们现在使用其他返回类型定义它。
那么当我用正确的返回类型定义它时,为什么它没有抱怨?
首先,int abs(int k)
的实现以编译形式(标准库)存在,而不是源代码形式。因此,如果已定义任何int abs(int k)
,则无法告知(在链接之前)。所以编译器很满意cstdlib
中的声明和我们提供的源中的定义。当它开始链接时,它只搜索已声明但尚未定义的函数(以便它可以复制定义(假设链接对静态库))。因此,链接器不会搜索int abs(int k)
的另一个定义。最后,我们给出的定义包含在结果二进制文件中。
答案 3 :(得分:-1)
我在<cstdlib>
内注意到以下内容:
#ifndef __CORRECT_ISO_CPP_STDLIB_H_PROTO
inline long
abs(long __i) { return __builtin_labs(__i); }
//...
当我使用long
,
#include <cstdlib>
#include <iostream>
long abs(long n) {
return n > 0 ? n : -n;
}
using namespace std;
int main() {
long k;
cin >> k;
cout << abs(k) << endl;
}
我收到了预期的错误:
error: call of overloaded 'abs(long int&)' is ambiguous
也许你的实现正在做类似的事情。
答案 4 :(得分:-1)
让我们将此代码修改为:
#include <iostream>
#include <cstdlib>
int abs(int n) {
std::cout << "default abs\n";
return n > 0 ? n : -n;
}
//using namespace std;
int main() {
int k;
std::cin >> k;
std::cout << std::abs(k) << std::endl;
}
它仍会呼唤你的腹肌。奇怪,是吧?好的,实际上std命名空间中没有int abs(int)
函数。这里没有模糊的调用,具体取决于使用的平台,因为实际的abs定义为等于:
std::intmax_t abs( std::intmax_t n );
但实际执行情况可能会有所不同,具体取决于多种因素。 你所做的就是你有过载功能或模板。只要您没有达到头文件中的确切定义,如果它与参数匹配得更好,则将使用您的函数。如果全局使用std命名空间,则可以通过std模板而不是std :: abs()函数尝试候选。这是在全球范围内使用命名空间std的一个警告。
实际上,在我的系统std :: abs中定义为来自全局范围的abs: 当然,你有一个来自全局范围的函数和这样的原型,由你自己定义,所以在我的情况下std :: abs调用等于:: abs调用。
#include <iostream>
#include <cstdlib>
int abs( long n ) {
std::cout << "default abs\n";
return n > 0 ? n : -n;
}
//using namespace std;
int main() {
int k;
std::cin >> k;
std::cout << std::abs(k) << std::endl;
}
现在它使用标准库函数并输出k的绝对值。
让我们看看cstdlib标头在特定情况下包含的内容:
_STD_BEGIN
using _CSTD size_t; using _CSTD div_t; using _CSTD ldiv_t;
using _CSTD abort; using _CSTD abs; using _CSTD atexit;
// and so on..
_STD_END
_STD_BEGIN定义为
#define _STD_BEGIN namespace std {
实际上我们有
namespace std {
using ::abs;
}
这样在全局范围内获得标识符abs的任何内容都变为std :: abs 这得到了前向声明的强制,因此在此定义之后定义的abs()是主题。因为语言语法允许这样做,所以在全局范围内重新定义库标识符可能会导致格式错误的程序或UB,在这种情况下,归结为标题中的哪些声明处于活动状态。
C ++标准库保留以下类型的名称:
- 宏
- 全球名称
- 具有外部链接的名称
如果程序在上下文中声明或定义名称 保留,除非本条款明确允许,否则 行为未定义。