2086. Overly generic type support for math functions

Section: 29.9 [c.math] Status: C++14 Submitter: Daniel Krügler Opened: 2011-09-22 Last modified: 2016-02-10

Priority: Not Prioritized

View all other issues in [c.math].

View all issues with C++14 status.

Discussion:

29.9 [c.math] ends with a description of a rule set for "sufficient overloads" in p11:

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.

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:

-2- Of the <math.h> and <complex.h> functions without an f (float) or l (long double) suffix, several have one or more parameters whose corresponding real type is double. For each such function, except modf, there is a corresponding type-generic macro. (footnote 313) The parameters whose corresponding real type is double in the function synopsis are generic parameters. Use of the macro invokes a function whose corresponding real type and type domain are determined by the arguments for the generic parameters. (footnote 314)

-3- Use of the macro invokes a function whose generic parameters have the corresponding real type determined as follows:

where footnote 314 clarifies the intent:

If the type of the argument is not compatible with the type of the parameter for the selected function, the behavior is undefined.

The combination of the usage of the unspecific term "cast" with otherwise no further constraints (note that C constraints the valid set to types that C++ describes as arithmetic types, but see below for one important difference) has the effect that it requires the following examples to be well-formed and well-defined:

#include <cmath>

enum class Ec { };

struct S { explicit operator long double(); };

void test(Ec e, S s) {
 std::sqrt(e); // OK, behaves like std::sqrt((float) e);
 std::sqrt(s); // OK, behaves like std::sqrt((float) s);
}

GCC 4.7 does not accept any of these examples.

I found another example where the C++ rule differs from the C set, but in this case I'm not so sure, which direction C++ should follow. The difference is located in the fact, that in C enumerated types are integer types as described in 6.2.5 p17 (see e.g. n1569 or n1256):

"The type char, the signed and unsigned integer types, and the enumerated types are collectively called integer types. The integer and real floating types are collectively called real types."

This indicates that in C the following code

#include <math.h>

enum E { e };

void test(void) {
  sqrt(e); // OK, behaves like sqrt((double) e);
}

seems to be well-defined and e is cast to double, but in C++ referring to

#include <cmath>

enum E { e };

void test() {
  std::sqrt(e); // OK, behaves like sqrt((float) e);
}

is also well-defined (because of our lack of constraints) but we must skip bullet 2 (because E is not an integer type) and effectively cast e to float. Accepting this, we would introduce a silent, but observable runtime difference for C and C++.

GCC 4.7 does not accept this example, but causes an ambiguity error among the three floating point overloads of sqrt.

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

Howard provided wording to solve the issue.

[2012, Kona]

Moved to Ready. The proposed wording reflects both original intent from TR1, and current implementations.

[2012, Portland: applied to WP]

Proposed resolution:

This wording is relative to the FDIS.

Change 29.9 [c.math] p11 as indicated:

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 tohave type float.