简体   繁体   中英

Why is std::modulus defined such that it only works for integer-numbers and not for floating point

From C++ Reference I take that std::modulus is defined such that it behaves like (for C++11)

template <class T> struct modulus {
   T operator() (const T& x, const T& y) const {return x%y;}
   typedef T first_argument_type;
   typedef T second_argument_type;
   typedef T result_type;
};

This means, that due to the use of % it can only be used for integer numbers. Is there a reason why it is not implemented such, that it could be used for floating point numbers as well?

Maybe I am missing the point, so all hints are very appreciated.

EDIT to reply to comment: So to give an example of what I would whish for with floating point numbers which is derived from the example from C++ Reference:

// modulus example
#include <iostream>     // std::cout
#include <functional>   // std::modulus, std::bind2nd
#include <algorithm>    // std::transform

int main () {
    float numbers[]={1.,2.,3.,4.,5.};
    float remainders[5];
    std::transform (numbers, numbers+5, remainders,    std::bind2nd(std::modulus<double>(),2.5));
    for (int i=0; i<5; i++)
        std::cout << numbers[i] << " is " <<    (remainders[i]==0?"even":"odd") << '\n';
    return 0;
}

But obviously this causes a compiler error like:

error C2296: '%' : illegal, left operand has type 'const double'
1>          C:\Program Files (x86)\Microsoft Visual Studio 10.0\VC\include\xfunctional(69) : while compiling class template member function 'double std::modulus<_Ty>::operator ()(const _Ty &,const _Ty &) const'
1>          with
1>          [
1>              _Ty=double
1>          ]
1>          main.cpp(16) : see reference to class template instantiation 'std::modulus<_Ty>' being compiled
1>          with
1>          [
1>              _Ty=double
1>          ]
1>C:\Program Files (x86)\Microsoft Visual Studio 10.0\VC\include\xfunctional(70): error C2297: '%' : illegal, right operand has type 'const double'

The various function objects in <functional> are intended to provide function objects for the various built-in operators. They are not intended as customization points or to extend beyond what the language defines. They also remained pretty much unchanged since they were proposed (in 1995 I think).

If you need different functionality, just create a suitable function object. With the current definition of C++ many of these function objects are mostly obsolete (they remain useful when the type of the function object needs to be spelled out). For example, this code is probably be more readable anyway

std::transform(numbers, numbers + 5, remainders,
    [](auto value){ return fmod(value, 2.5); });

... or even

std::transform(std::begin(numbers), std::end(numbers), std::begin(remainders),
   [](auto value){ return fmod(value, 2.5); });

Why it doesn't work

According to cplusplus.com modulus page :

Binary function object class whose call returns the result of the modulus operation between its two arguments (as returned by operator %)

Since this is a wrapper to the legacy operator, which doesn't support floating point types, there is no such specialization.

Possible solution (Not the suggested one)

You could just add it yourself:

namespace std {
  template <>
  struct modulus<double> {
    double operator()(const double &lhs, const double &rhs) const {
      return fmod(lhs, rhs);
    }
  };
} // namespace std

And then it will work as intended:

int main() {
    std::cout << "fmod of 5.3 / 2.0 is " << fmod (5.3,2) << std::endl;
    std::cout << "fmod of 5.3 / 2.0 is " << std::modulus<double>()(5.3, 2.0) << std::endl;
}

Note:

As comments pointed out, this type of overloading is not highly recommended by the cpp standard, so be careful when using it:

The behavior of a C++ program is undefined if it adds declarations or definitions to namespace std or to a namespace within namespace std unless otherwise specified. A program may add a template specialization for any standard library template to namespace std only if the declaration depends on a user-defined type and the specialization meets the standard library requirements for the original template and is not explicitly prohibited.

Although our specialization meets the standard library requirements for the original template and even though it (probably) takes place in a private scope (*.cpp file), it still is considered undefined behavior (since we don't depend on a user-defined type).

Better solution

Instead, we could use this nifty trick to become 100% legitimate:

template < typename T, typename = typename std::is_floating_point<T>::type >
struct ModulusFP {
  ModulusFP(T val) : val(val) {}
  T val;

  operator T() const { return val; }
};

namespace std {
  template < typename T >
  struct modulus<ModulusFP<T>> : binary_function<T, T, T> {
    ModulusFP<T> operator()(const ModulusFP<T> &lhs, const ModulusFP<T> &rhs) const {
      return fmod(T(lhs), T(rhs));
    }
  };
} // namespace std

And then the code still works as intended, both for trivial uses and for more complicated ones:

int main() {
    std::cout << "fmod of 5.3 / 2.0 is " << fmod (5.3,2) << std::endl;
    std::cout << "fmod of 5.3 / 2.0 is " << std::modulus<ModulusFP<double>>()(5.3, 2.0) << std::endl;

    float numbers[]={1.,2.,3.,4.,5.};
    float remainders[5];
    std::transform (numbers, numbers+5, remainders, std::bind2nd(std::modulus<ModulusFP<float>>(), 2.5));
    for (int i=0; i<5; i++)
        std::cout << numbers[i] << " is " <<    (remainders[i]==0?"even":"odd") << '\n';
    return 0;
}

You can create your own version of the function that also handles floating point numbers:

template<class T, class = typename std::is_floating_point<T>::type>
struct MyModulus : std::modulus<T>
{};

template<class T>
struct MyModulus<T, std::true_type> : std::binary_function<T, T, T>
{
    T operator()(T a, T b) const { return std::fmod(a, b); }
};

Since you use C++11, prefer using lambdas to std::bind . Lambdas are more efficient and easier to write and read:

std::transform(numbers, numbers+5, remainders, [](double a) { 
    return std::fmod(a, 2.5); 
});

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM