Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Different behaviour of fmod (and others) under c++11, in Visual Studio at least

I have some example code that is behaving differently under Visual C++ 2012 with the new C++11 headers than it used to under VC++ 2010. It concerns what happens when you call the std::fmod function that you get when you include cmath, and when the arguments you pass are not doubles, but are rather classes that have an implicit conversion to double operator:

#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;
}

Rather to my surprise this calls the version of fmod that takes two floats, rather than the version of fmod that takes two doubles.

So my question is, is this the correct behaviour given the C++ 11 standard? The only information I can find on the behaviour is in the cppreference.com documentation here, which says (emphasis mine):

If any argument has integral type, it is cast to double. If any other argument is long double, then the return type is long double, otherwise it is double.

However the implementation in the Visual Studio header files would appear to implement "otherwise it's a float".

Any one know what the intention is :-) ?

Having run the example through an online c++11 version of GCC ( I don't have simple access to a recent copy of GCC otherwise) it would appear to be calling the "double" version of fmod, which is what I naively expect.

For clarity, I am using

Microsoft (R) C/C++ Optimizing Compiler Version 17.00.51106.1 for x86

which is what comes with

Microsoft Visual Studio Express 2012 for Windows Desktop Version 11.0.51106.01 Update 1

like image 843
Tom Quarendon Avatar asked Mar 22 '13 14:03

Tom Quarendon


1 Answers

This is related to this question of mine. The reason is, that in order to provide the additional overloads demanded by the standard (and quoted in your question) VS 2012 defines general function templates for all the 2-argument math functions. So you don't actually call fmod(float, float) but fmod<Num>(Num, Num) in the first place.

The reason this templated function is preferred over the plain double version is, because the double version would require a user-defined conversion from Num to double, whereas the template version is directly instantiatable.

But the actual fundamental type to call the fmod function for is then determined by this type trait from <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;
    };

What this does is check the promoted type _Promote_to_float (which in your case is again Num, because it only checks if its integral, which Num clearly isn't) to all the floating point types until it matches, which it doesn't and thus results in the else case of float.

The reason for this erroneous behaviour is, that those additional math overloads were never meant to be provided for each and every type, but only for the builtin arithmetic types (and the ambigous standard wording is about to be fixed, as stated in my answer to the linked question). So in all this type deduction mechanic explained above VS 2012 assumes that the passed in types are either builtin integral types or, if not, then builtin floating point types, which of course fails for Num. So the actual problem is that VS provides too generic math functions whereas they should only provide overloads for builtin types (like properly done for the 1-argument functions already). As stated in the linked answer, I already filed a bug for this.

EDIT: In fact (as you also realized) even if they would follow the currently ambigous standard wording and providing generic function templates was required, they should still have defined the actual promoted type of those generic arguments as double instead of float. But I think the actual problem here is that they completely ignore the possible presence of non-builtin types in this whole type conversion process (since for builtin types their logic works perfectly fine).

But according to the current ambiguous standard wording (which is already planned for changing, though) in section 26.8 [c.math] they are indeed correctly deducing the promoted type as float (per the 3rd case):

there shall be additional overloads sufficient to ensure:

  1. If any argument corresponding to a double parameter has type long double, then all arguments corresponding to double parameters are effectively cast to long double.
  2. Otherwise, if any argument corresponding to a double parameter has type double or an integer type, then all arguments corresponding to double parameters are effectively cast to double.
  3. Otherwise, all arguments corresponding to double parameters are effectively cast to float.
like image 64
Christian Rau Avatar answered Nov 16 '22 03:11

Christian Rau