10

我有一些示例代码在 Visual C++ 2012 下使用新的 C++11 标头的行为与在 VC++ 2010 下的行为不同。它涉及调用包含 cmath 时获得的 std::fmod 函数时会发生什么,并且当您传递的参数不是双精度,而是具有隐式转换为双精度运算符的类时:

#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 的“双”版本,这是我天真地期望的.

为了清楚起见,我正在使用

Microsoft (R) C/C++ 优化编译器版本 17.00.51106.1 for x86

这是附带的

Microsoft Visual Studio Express 2012 for Windows 桌面版 11.0.51106.01 更新 1

4

2 回答 2

5

这与我的这个问题有关。原因是,为了提供标准要求的额外重载(并在您的问题中引用)VS 2012 为所有 2 参数数学函数定义了通用函数模板。所以你实际上并没有打电话fmod(float, float),而是fmod<Num>(Num, Num)首先打电话。

这个模板化函数比普通double版本更受欢迎的原因是,因为双版本需要用户定义的从Numto转换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-argument 函数正确完成的那样)。如链接答案中所述,我已经为此提交了一个错误。

编辑: 事实上(您也意识到)即使他们遵循当前模棱两可的标准措辞并需要提供通用函数模板,他们仍然应该将这些通用参数的实际提升类型定义为double而不是float. 但我认为这里的实际问题是他们完全忽略了在整个类型转换过程中可能存在的非内置类型(因为对于内置类型,它们的逻辑工作得非常好)。

但是根据第26.8 节 [c.math]中当前模棱两可的标准措辞(尽管已经计划更改),他们确实正确地将提升的类型推断为float(根据第 3 种情况):

应有足够的额外过载以确保:

  1. 如果对应于 double 形参的任何实参的类型为 long double,则对应于 double 形参的所有实参都将有效地转换为 long double。
  2. 否则,如果对应于 double 参数的任何实参具有 double 类型或整数类型,则对应于 double 形参的所有实参都将有效地强制转换为 double。
  3. 否则,对应于双参数的所有参数都被有效地转换为浮点数。
于 2013-03-22T14:57:01.627 回答
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));
}
于 2013-03-22T15:04:32.107 回答