4

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'
  • @AlgirdasPreidžius Exactly the same way as for integers. – Maxim Egorushkin Oct 09 '17 at 09:30
  • 2
    All these functors (plus, minus, etc.) are defined to use the built-in operators so that these can be passed as parameters (which you cannot do with `+` or `-` directly). It was not intended to add extra functionality. – Bo Persson Oct 09 '17 at 09:32
  • @AlgirdasPreidžius Try it. – Maxim Egorushkin Oct 09 '17 at 09:32
  • for instance if I want to wrap an angle to [-2pi,2pi] like mod(angle,2pi) –  Oct 09 '17 at 09:32
  • 3
    @AlgirdasPreidžius Most `floats` will lose massive amounts of precision when cast to `int`, and a modulus operation on the result will therefore deliver the wrong answer. This is rather obvious. – user207421 Oct 09 '17 at 09:36
  • @AlgirdasPreidžius Basic version: `double modulus(double a, double b) { return a - b * std::floor(a / b); }` – Maxim Egorushkin Oct 09 '17 at 09:36
  • 4
    @AlgirdasPreidžius So `fmod()` doesn't exist? – user207421 Oct 09 '17 at 09:37
  • 1
    @AlgirdasPreidžius I've addressed that. Because of the increased range. Your objections seem extremely obtuse. – user207421 Oct 09 '17 at 09:39
  • This is nonsense mathematically speaking. what is the modulo of (1/Pi)? EDIT: i meant 1%PI – Guillaume.P Oct 09 '17 at 09:57
  • @GuillaumePaniagua http://en.cppreference.com/w/cpp/numeric/math/fmod – Bob__ Oct 09 '17 at 10:00
  • @Guillaume Paniagua I do not argue it is mathematically strange. However, in programming there exist constructs like fmod. Your question of what modulus of (1/Pi) is makes no sense, as this is merely a division. Or do you mean mod(1,pi)? –  Oct 09 '17 at 10:01
  • Yeah but they called it fmod not mod for that reason this is not a modulus. "%" is the mathematical equivalent of euclidean division rest calculus and only this. We can't reinvent mathematics this is the answer and reason why modulus does not perform this kind of operation. If the question is how can we perform the operation then fair enough. – Guillaume.P Oct 09 '17 at 10:06
  • @GuillaumePaniagua That's pretty confused. `%` is certainly a remainder operator, and `fmod()` is certainly a modulus function, but `fmod()` versus `mod()` has nothing to do with it. – user207421 Oct 09 '17 at 10:10
  • Programming languages often do not do the same thing as you would expect from a mathematically point of view. a = a + b is perfectly good in C++, even for non-zero b. However, in mathematics this is not really the case. A vector in C++ is not a mathematical vector. I am pretty sure this could go on a while. So I do not really see, why modulus in C++ cannot support something for floating point numbers which behaves like fmod. –  Oct 09 '17 at 10:26

3 Answers3

4

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); });
Dietmar Kühl
  • 150,225
  • 13
  • 225
  • 380
  • 1
    This will not work since % is not working for floating point numbers, or am I wrong? –  Oct 09 '17 at 10:21
  • @Pi: sure - I was concentrating on getting the other stuff right: replaced `value % 2.5` by `fmod(value, 2.5)`... – Dietmar Kühl Oct 09 '17 at 11:07
1

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); 
});
Maxim Egorushkin
  • 131,725
  • 17
  • 180
  • 271
1

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;
}
Cubbi
  • 46,567
  • 13
  • 103
  • 169
Daniel Trugman
  • 8,186
  • 20
  • 41
  • 1
    Can you specialize an `std` template for a built-in type and in a different namespace? – Maxim Egorushkin Oct 09 '17 at 09:51
  • @MaximEgorushkin What 'different namespace'? – user207421 Oct 09 '17 at 09:54
  • @EJP, I guess he means, not from inside the `std` namespace – Daniel Trugman Oct 09 '17 at 09:57
  • `[namespace.std]` _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._ – Maxim Egorushkin Oct 09 '17 at 09:58
  • @MaximEgorushkin Very good, but there is nothing there about 'from a different namespace'. `std::modulus` belongs to the `std::` namespace, and can only be overridden, if at all, from that namespace. Your original comment, or question, didn't make sense. – user207421 Oct 09 '17 at 10:01
  • 3
    -1. According to http://en.cppreference.com/w/cpp/language/extending_std: "It is allowed to add template specializations for any standard library template to the namespace std only if the declaration depends on a ***user-defined type***". Specializing with `double` is undefined behavior. – lisyarus Oct 09 '17 at 10:02
  • @EJP To define a template specialization the code must open namespace `std` first. It does not. – Maxim Egorushkin Oct 09 '17 at 10:02
  • @MaximEgorushkin I agree, but there is nothing in this answer that precludes that, or that justifies your original comment/question. – user207421 Oct 09 '17 at 10:03
  • 1
    @EJP I agree to disagree agreeably. – Maxim Egorushkin Oct 09 '17 at 10:05
  • @MaximEgorushkin Sure, but I'm still waiting for a answer to 'what different namespace?' – user207421 Oct 09 '17 at 10:11
  • @lisyarus, I added a comment regarding just that and an implementation that is 100% legitimate – Daniel Trugman Oct 09 '17 at 10:13
  • @DanielTrugman Nice! Still a -1 since all this does not answer the actual question (as an example, Dietmar Kühl's answer *does* provide an answer, in the very first sentence). – lisyarus Oct 09 '17 at 10:14
  • Also note that your functional object does not define the required typedefs, so that it does not compile with `std::bind`. A rather poor quality answer. – Maxim Egorushkin Oct 09 '17 at 11:05
  • @MaximEgorushkin, before you start trashing my answer, please make sure you know what you are talking about. It works perfectly with `std::bind`. Prepared an [example](https://repl.it/MTNj/latest) for you. – Daniel Trugman Oct 09 '17 at 11:24
  • So many things wrong with this answer, yet you are asking me to know what I am talking about, lol. https://ideone.com/B2ljwg : `error: specialization of ‘template struct std::modulus’ in different namespace`, `error: no type named ‘first_argument_type’ in ‘struct std::modulus` – Maxim Egorushkin Oct 09 '17 at 11:29
  • 2
    This is undefined, period. Nothing gray about it. – T.C. Oct 09 '17 at 12:16
  • @T.C., Updated answer and avoided this phrase. – Daniel Trugman Oct 09 '17 at 12:40
  • @MaximEgorushkin, you were right, newer GCC versions do complain about both the specialization in a different namespace (Wrapped specialization using `namespace std { .. }` instead of using `std::`) and about the `std::bind` (Improved by inheriting from `std::binary_function` to resolve that). – Daniel Trugman Oct 09 '17 at 12:41