我有一些示例代码在Visual C ++ 2012下使用新的C ++ 11标头,而不是VC ++ 2010中的代码。 它涉及当您调用包含cmath时获得的std :: fmod函数时发生的情况,以及当您传递的参数不是双精度时,而是隐式转换为double运算符的类:
#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文档here,其中说明了(强调我的):
如果任何参数具有整数类型,则将其强制转换为double。如果任何其他参数是long double,则返回类型为long double,否则为double 。
然而,Visual Studio头文件中的实现似乎会实现“否则它是一个浮动”。
任何人都知道目的是什么:-)?
通过在线c ++ 11版本的GCC运行示例(我没有简单访问最近的GCC副本),它似乎是调用fmod的“双”版本,这是什么我天真地期待。
为清楚起见,我正在使用
Microsoft(R)C / C ++优化编译器版本17.00.51106.1 for x86
随附
Microsoft Visual Studio Express 2012 for Windows桌面版本11.0.51106.01 Update 1
答案 0 :(得分:5)
这与this question of mine有关。原因是,为了提供标准所要求的额外重载(并在您的问题中引用),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
的其他情况。
这种错误行为的原因是,那些额外的数学重载从来都不是为每种类型提供的,而是仅针对内置算术类型(并且即将修复标准的标准措辞,如我对相关问题的回答)。因此,在上面解释的所有这种类型的演绎机制中,VS 2012假定传入的类型是内置的整数类型,或者如果不是,那么内置浮点类型,当然Num
失败。所以实际问题是VS提供了过于通用的数学函数,而它们应该只为内置类型提供重载(就像已经为1参数函数正确完成的那样)。如链接的答案中所述,我已经为此提交了一个错误。
编辑: 事实上(正如您也意识到的那样)即使他们会遵循当前模糊的标准措辞并且需要提供通用功能模板,他们仍然应该定义实际的推广类型那些通用参数为 double
而不是float
。但我认为这里的实际问题是它们完全忽略了整个类型转换过程中可能存在的非内置类型(因为对于内置类型,它们的逻辑非常好)。
但是根据当前模糊的标准措辞(虽然已经计划改变),在 26.8 [c.math] 部分,他们确实正确地将推广类型推断为float
(按第3种情况):
应该有足够的额外超载来确保:
- 如果对应于double参数的任何参数的类型为long double,则所有与double参数对应的参数都是 有效地施展到长双。
- 否则,如果对应于double参数的任何参数具有double类型或整数类型,则所有参数都对应于 双参数有效地加倍。
- 否则,所有与double参数对应的参数都被有效地转换为float。
醇>
答案 1 :(得分:0)
正如克里斯蒂安在他的相关问题和答案中指出的那样,这是严格阅读标准所要求的行为。
但是,您可以非常轻松地解决所有版本的问题:
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));
}