简体   繁体   English

为什么定义std :: modulus使得它仅适用于整数而不适用于浮点数

[英]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) C ++参考我认为std :: modulus的定义使得它的行为类似于(对于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: 编辑回复评论:那么举一个例子来说明我想要的浮点数,这是从C ++参考中的例子得出的:

// 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. <functional>中的各种函数对象旨在为各种内置运算符提供函数对象。 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). 自提出以来,它们也几乎保持不变(1995年我认为)。

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). 对于C ++的当前定义,许多这些函数对象大多已经过时(当需要拼写出函数对象的类型时,它们仍然有用)。 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 : 根据cplusplus.com modulus页面

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: 正如评论所指出的那样,cpp标准并不强烈推荐这种类型的重载,因此在使用它时要小心:

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. 如果C ++程序向命名空间std或命名空间std中的命名空间添加声明或定义,则它是未定义的,除非另有说明。 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. 只有当声明取决于用户定义的类型并且特化符合原始模板的标准库要求且未明确禁止时,程序才可以将任何标准库模板的模板特化添加到命名空间std。

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). 虽然我们的专业化符合原始模板的标准库要求,即使它(可能)发生在私有作用域(* .cpp文件)中,它仍然被认为是未定义的行为(因为我们不依赖于用户定义的类型)。

Better solution 好的解决方案

Instead, we could use this nifty trick to become 100% legitimate: 相反,我们可以使用这个漂亮的技巧成为100%合法:

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 . 由于您使用C ++ 11,因此更喜欢将lambdas用于std::bind Lambdas are more efficient and easier to write and read: Lambdas更高效,更易于编写和阅读:

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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