重载调用abs(double)模糊不清

67

我有以下的C++代码:

#include <math.h>
#include <cmath.h>      // per http://www.cplusplus.com/reference/clibrary/cmath/abs/

// snip ...

if ( (loan_balance < 0) && (abs(loan_balance) > loan_payment) ) {
    ...
}

并且 make 在以下情况下崩溃:

error: call of overloaded 'abs(double)' is ambiguous

还有其他感兴趣的内容:

/usr/include/stdlib.h:785: note: candidates are: int abs(int)

我该如何指定编译器调用 cmath.h 中可以处理浮点数的 abs() 函数?

编译器信息(不确定是否相关):

[some_man@some_box ~/some_code]#  gcc -v
Using built-in specs.
Target: i386-redhat-linux
Configured with: ../configure --prefix=/usr --mandir=/usr/share/man --infodir=/usr    /share/info --enable-shared --enable-threads=posix --enable-checking=release --with-system-zlib --enable-__cxa_atexit --disable-libunwind-exceptions --enable-libgcj-multifile --enable-languages=c,c++,objc,obj-c++,java,fortran,ada --enable-java-awt=gtk --disable-dssi --enable-plugin --with-java-home=/usr/lib/jvm/java-1.4.2-gcj-1.4.2.0/jre --with-cpu=generic --host=i386-redhat-linux
Thread model: posix
gcc version 4.1.2 20080704 (Red Hat 4.1.2-44)

6
引用的cplusplus页面并没有说要包含cmath.h,它说的是cmath。这是math.h的C++版本。不要同时包含两者。 - Rob Kennedy
4个回答

57
标头<math.h>是C标准库头文件。它在全局命名空间中定义了很多内容。标头<cmath>是该标头的C++版本。它在std 命名空间中定义了基本相同的内容。(存在一些差异,例如C++版本提供了某些函数的重载,但这并不重要。)标头<cmath.h>不存在。
由于供应商不想维护本质相同的两个版本,他们想出了不同的方法,以在幕后只有一个版本。通常,这是 C 标头 (因为 C++ 编译器能够解析它,而反过来则不行),而 C++ 标头只是包含它并将所有内容都放入命名空间std 中。或者通过一些宏魔术来解析具有或不具有namespace std 的相同头文件。此外,在某些环境中,如果头文件没有文件扩展名,则会很尴尬(例如编辑器无法突出显示代码等)。因此,一些供应商会使<cmath>成为包含具有.h扩展名的其他头文件的一行代码。或者有些人会将所有匹配<cblah>的包含都映射到<blah.h> (通过宏魔术,当定义了__cplusplus时,它成为C++标头,否则成为C标头),或者<cblah.h>或其他。
这就是为什么在某些平台上包括像 <cmath.h> 这样的文件,虽然本不应存在,但最初会成功,尽管后来可能会使编译器出现严重错误的原因。
我不知道您使用哪个std lib实现。我想它是随GCC一起提供的那个,但我不确定,所以我不能确切地解释发生了什么事。但肯定是一种上述特定于供应商的混合方法,您包含了一个本不应该包含的头文件。也许是其中<cmath>映射到带有特定宏的<cmath.h>的那个,而您没有定义这个宏,所以您最终得到了两个定义。
请注意,但此代码仍然不应编译:
#include <cmath>

double f(double d)
{
  return abs(d);
}

全局命名空间中不应该有abs()函数(应使用std::abs())。

然而,根据上述实现技巧,可能确实存在该函数。稍后移植这样的代码(或仅尝试在下一个版本中编译它,但供应商不允许)可能会非常繁琐,因此您应该注意这一点。


关于“这段代码仍然不应该编译”,实际上在所有当前的实现中都可以编译:https://gcc.godbolt.org/z/dfzhY7chc,但不幸的是结果是实现定义的。 - Fedor

42

这归结为以下问题:math.h源自C,已经存在超过10年。在math.h中,由于其原始性质,abs()函数“本质上”只适用于整数类型,如果要获得双精度浮点数的绝对值,则必须使用fabs()。 当创建C++时,它采用了math.h并将其改名为cmathcmath本质上是C ++ 的 math.h,但对 C++ 进行了改进。它改进了诸如区分fabs()abs()之类的事情,并仅将abs()用于双精度浮点数和整数类型。 总之,两种方法: 使用 math.h 并针对整数使用abs(),针对双精度浮点数使用fabs() 或 使用 cmath 并对所有内容都使用 abs(更容易,建议使用)

希望这能帮助任何有同样问题的人!


19

使用fabs()替代abs(),二者相同但是前者适用于浮点数而非整数。


6
std命名空间中,这并不完全准确,因为abs会对floatdouble等进行重载。 - Flexo
1
修复gcc6错误:调用重载的'abs(uint32_t)'是不明确的。 - Sérgio
@Sérgio 我认为你应该删除对 abs 的调用,因为 uint32_t 是无符号的。(相反,您正在将 uint32_t 隐式转换为 double 以进行 fabs(double) ,然后我假设将结果转换回整数!) - Raptor007
我不记得我在哪里使用了这个补丁,我也找不到它在哪里,我在谷歌上搜索了一下,但我同意你的看法...我想要审查一下这个gcc6修复。谢谢。 - Sérgio

4

在我的情况下,当使用labs()而不是abs()时,我解决了这个问题。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接