将非算术类型作为参数传递给 cmath 函数是否有效?

Is it valid to pass non-arithmetic types as arguments to cmath functions?

给定以下用户定义类型 S 并具有到 double 的转换函数:

struct S
{
   operator double() { return 1.0;}
};

以及以下使用类型 Scmath 函数的调用:

#include <cmath>

void test(S s) {
   std::sqrt(s); 
   std::log(s); 
   std::isgreater(s,1.0);
   std::isless(s,1.0);
   std::isfinite(s) ;
}

此代码使用 libstdc++ 使用 gcc 进行编译(see it live) but with clang using libc++ it generates errors for several of the calls (see it live) with the following error for isgreater:

error: no matching function for call to 'isgreater'
   std::isgreater(s,1.0);
   ^~~~~~~~~~~~~~

note: candidate template ignored: disabled by 'enable_if' [with _A1 = S, _A2 = double]
std::is_arithmetic<_A1>::value &&
^

以及 isless and isfinite, so libc++ expects the arguments for those calls to be arithmetic types which S is not, we can confirm this by going to the source for libc++ cmath header 的类似错误。虽然,libc++.

中的所有 cmath 函数对算术类型的要求并不一致

所以问题是,将非算术类型作为参数传递给 cmath 函数是否有效?

TL;DR

根据标准,将非算术类型作为参数传递给 cmath 函数是有效的,但缺陷报告 2068 认为最初的意图是应该限制 cmath 函数到算术类型,使用非算术参数似乎最终会变成病式。因此,尽管根据缺陷报告 2068.

,使用非算术类型作为参数在技术上是有效的似乎值得怀疑

详情

cmath header is covered in the draft standard section 26.8 [c.math] provides an additional float and long double overload for the each function defined in math.h 接受一个 double 参数,而且 11 段提供了 足够的重载 和说:

Moreover, 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.

这似乎在 C++11 中有效

在 C++11 部分 26.8 [c.math] 中不包含任何禁止对 cmath 函数使用非算术参数的限制.在问题的每种情况下,我们都有一个可用的重载,它需要 double 个参数,并且应该通过 overload resolution 选择这些参数。

缺陷报告 2086

但对于 C++14,我们有 defect report 2086: Overly generic type support for math functions,它认为 26.8 [c.math] 部分的初衷是限制 cmath 函数仅对 算术类型 有效,这将模仿它们在 C:

中的工作方式

My impression is that this rule set is probably more generic as intended, my assumption is that it is written to mimic the C99/C1x rule set in 7.25 p2+3 in the "C++" way [...] (note that C constraints the valid set to types that C++ describes as arithmetic types, but see below for one important difference) [...]

并说:

My current suggestion to fix these problems would be to constrain the valid argument types of these functions to arithmetic types.

并改写第 26.811 说(强调我的 ):

Moreover, there shall be additional overloads sufficient to ensure:

  1. If any arithmetic argument corresponding to a double parameter has type long double, then all arithmetic arguments corresponding to double parameters are effectively cast to long double.
  2. Otherwise, if any arithmetic argument corresponding to a double parameter has type double or an integer type, then all arithmetic arguments corresponding to double parameters are effectively cast to double.
  3. Otherwise, all arithmetic arguments corresponding to double parameters are effectively cast to are effectively cast to have type float.

所以这在 C++14 中是无效的?

好吧,尽管意图如此,但从技术上看,它在 libc++ bug report: incorrect implementation of isnan and similar functions 中的讨论中的评论中仍然有效:

That may have been the intent, but I don't see any way to read the standard's wording that way. From the example in comment#0:

std::isnan(A());

There are no arguments of arithmetic type, so none of the bullets in 26.8/11 apply. The overload set contains 'isnan(float)', 'isnan(double)', and 'isnan(long double)', and 'isnan(float)' should be selected.

因此,DR 2086 对段落 11 的改写不会导致调用 float 的格式错误, doublelong double 重载可用于非算术参数。

技术上有效但使用有问题

因此,尽管 C++11 和 C++14 标准没有将 cmath 函数限制为算术参数 DR 2068 争论 26.8 段落 11 的意图是限制 cmath 函数只接受算术参数,显然是为了弥补 C++14 中的漏洞,但没有提供足够强大的限制。

依赖一项在未来版本的标准中可能会变得格式错误的功能似乎值得怀疑。由于我们存在实现分歧,因此在这些情况下,任何依赖于将非算术参数传递给 cmath 函数的代码都是不可移植的,因此仅在有限的情况下有用。我们有另一种解决方案,即显式地将非算术类型转换为算术类型,这样就绕过了整个问题,我们再也不用担心代码变得病态了,而且它是可移植的:

std::isgreater( static_cast<double>(s) ,1.0)
                ^^^^^^^^^^^^^^^^^^^^^^

正如 Potatoswatter 指出的那样,使用一元 + 也是一种选择:

std::isgreater( +s ,1.0)

更新

如T.C。在 C++11 中指出,可以争论 26.8 段落 11 项目符号 3 适用,因为参数既不是 long doubledouble 也不是整数,因此 S 类型的参数应该首先转换为 float。请注意,如缺陷报告所示 gcc 从未实施过,据我所知 clang.

也没有实施