fmod(和其他)在c++11下的不同行为,至少在Visual Studio中
Different behaviour of fmod (and others) under c++11, in Visual Studio at least
我有一些示例代码,在Visual c++ 2012下与新的c++ 11头文件的行为不同,而不是在vc++ 2010下。它涉及调用std::fmod函数时发生的情况,该函数在包含cmath时得到,并且传递的参数不是双精度,而是具有隐式转换为双精度操作符的类:
#include <cmath>
class Num {
double d_;
public:
Num(double d) : d_(d) {}
operator double() const { return d_; }
};
int main(int argc, char* argv[]) {
Num n1(3.14159265358979323846264338327950288419716939937510);
Num n2(2.0);
double result1 = fmod((double)n1, (double)n2);
double result2 = fmod((float)n1, (float)n2);
double result3 = fmod(n1, n2);
if (result2==result1) std::cout << "fmod(double, double) returns the same as fmod(float,float)" << std::endl;
if (result3==result1) std::cout << "fmod(Num, Num) returns the same as fmod(double,double)" << std::endl;
if (result3==result2) std::cout << "fmod(Num, Num) returns the same as fmod(float,float)" << std::endl;
}
令我惊讶的是,这调用了fmod的两个浮点数版本,而不是fmod的两个双精度数版本。
所以我的问题是,这是正确的行为给定的c++ 11标准?我能找到的关于行为的唯一信息是在这里的cppreference.com文档中,它说(强调我的):
如果任何实参是整型,则将其强制转换为double类型。如果任何其他实参为long double,则返回类型为long double, 否则返回类型为double。
然而,Visual Studio头文件中的实现似乎实现了"否则它是一个浮点数"。
有人知道这是什么意思吗?
通过在线c++11版本的GCC运行这个例子(我没有简单的访问GCC的最新副本),它似乎调用了fmod的"double"版本,这是我天真的期望。
为清楚起见,我使用
Microsoft (R) C/c++ optimization Compiler Version 17.00.51106.1 for x86
也就是
Microsoft Visual Studio Express 2012 for Windows Desktop Version 11.0.51106.01 Update 1
这与我的这个问题有关。原因是,为了提供标准(并在您的问题中引用)所需的额外重载,VS 2012为所有2参数数学函数定义了通用函数模板。所以你实际上不叫fmod(float, float)
,而是叫fmod<Num>(Num, Num)
。
此模板化函数优于普通double
版本的原因是,因为双版本将需要从Num
到double
的用户定义转换,而模板版本是可直接实例化的。
但是调用fmod
函数的实际基本类型是由<xtgmath.h>
的这个类型特征决定的:
template<class _Ty>
struct _Promote_to_float
{ // promote integral to double
typedef typename conditional<is_integral<_Ty>::value,
double, _Ty>::type type;
};
template<class _Ty1,
class _Ty2>
struct _Common_float_type
{ // find type for two-argument math function
typedef typename _Promote_to_float<_Ty1>::type _Ty1f;
typedef typename _Promote_to_float<_Ty2>::type _Ty2f;
typedef typename conditional<is_same<_Ty1f, long double>::value
|| is_same<_Ty2f, long double>::value, long double,
typename conditional<is_same<_Ty1f, double>::value
|| is_same<_Ty2f, double>::value, double,
float>::type>::type type;
};
它的作用是将提升后的类型_Promote_to_float
(在您的示例中还是Num
,因为它只检查它是否为整型,而Num
显然不是整型)检查到所有浮点类型,直到它匹配为止,但它不匹配,从而导致float
的else情况。
这种错误行为的原因是,这些额外的数学重载从来没有打算为每个类型提供,而只是为内置的算术类型提供(并且含糊的标准措辞即将被修复,正如我对链接问题的回答所述)。因此,在以上解释的所有类型演绎机制中,VS 2012假设传入类型要么是内置整型,要么是内置浮点型,这当然对Num
来说是失败的。所以实际的问题是VS提供了太泛型的数学函数,而它们应该只为内置类型提供重载(就像已经为1参数函数做的那样)。如链接答案中所述,我已经为此提交了一个bug。
EDIT:事实上(正如您所意识到的),即使他们遵循当前含糊不清的标准措辞,并且需要提供泛型函数模板,他们仍然应该将这些泛型参数的实际提升类型定义为double
而不是float
。但我认为这里的实际问题是,它们完全忽略了在整个类型转换过程中可能存在的非内置类型(因为对于内置类型,它们的逻辑工作得非常好)。
但是根据26.8节中目前模棱两可的标准措辞(尽管已经计划更改)[c]。他们确实正确地将提升的类型推断为float
(根据第三种情况):
应该有额外的过载,足以确保:
- 如果任何与双形参对应的实参类型为long double,则所有与双形参对应的实参类型均为long double
- 否则,如果任何与双精度形参相对应的实参类型为double或整型,则所有与double形参被有效地转换为double。
- 否则,所有与双形参对应的实参都被有效地强制转换为float。
正如Christian在他的链接问答中指出的那样,这是严格阅读标准所要求的行为。
然而,你可以很容易地在所有版本中解决这个问题:
Num fmod(const Num a, const Num b)
{
const double cvt_a = a;
const double cvt_b = b;
return Num(fmod(cvt_a, cvt_b));
}
- Visual Studio 2015:Extern "C" 和 "export" 关键字
- 为cl.exe(Visual Studio代码)指定命令行C++版本
- 试图在visual studio上用C++创建一个桌面应用程序
- 如何在MS Visual Studio 2019中运行QT UI
- 如何使用Visual Studio 2017在C++中为参数化对象数组使用唯一指针
- Visual Studio Code - C++ Debugger 無法啟動
- Visual Studio 2019:插入多个C++风格的单行注释
- Visual Studio Code "undefined reference to `WinMain@16'"
- Visual studio代码重构似乎不起作用(例如,重命名符号-f2)
- Visual Studio中的函数声明和函数定义问题
- 如何指定我希望我的LIB链接到的DLL文件?-Visual Studio 2019
- Visual Studio mkl_link_tool.exe链接错误
- Visual Studio(或任何其他工具)能否将地址解释为调用堆栈(boost上下文)的开头
- 不同的Visual Studio版本中缺少.dll
- 用Visual Studio在C++中嵌入Julia
- Visual Studio 2017循环自动向量化问题
- 有没有办法在远程设备上打开和编辑visual Studio 2017解决方案
- 尝试使用继承和模板实现CRTP.Visual Studio正在生成编译器错误
- Visual Studio在尝试读取resource.txt文件时崩溃
- Visual Studio 2017 不允许我创建 C++ 专用模板