This is an unofficial snapshot of the ISO/IEC JTC1 SC22 WG21 Core Issues List revision 115e. See http://www.open-std.org/jtc1/sc22/wg21/ for the official list.

2024-11-11


425. Set of candidates for overloaded built-in operator with float operand

Section: 12.5  [over.built]     Status: CD1     Submitter: Daniel Frey     Date: 30 June 2003

[Voted into WP at March 2004 meeting.]

During a discussion over at the boost mailing list (www.boost.org), we came across the following "puzzle":

  struct A {
    template< typename T > operator T() const;
  } a;

  template<> A::operator float() const
  {
    return 1.0f;
  }

  int main()
  {
    float f = 1.0f * a;
  }

The code is compiled without errors or warnings from EDG-based compilers (Comeau, Intel), but rejected from others (GCC, MSVC [7.1]). The question: Who is correct? Where should I file the bug report?

To explain the problem: The EDG seems to see 1.0f*a as a call to the unambiguous operator*(float,float) and thus casts 'a' to 'float'. The other compilers have several operators (float*float, float*double, float*int, ...) available and thus can't decide which cast is appropriate. I think the latter is the correct behaviour, but I'd like to hear some comments from the language lawyers about the standard's point of view on this problem.

Andreas Hommel: Our compiler also rejects this code:

Error   : function call 'operator*(float, {lval} A)' is ambiguous
'operator*(float, unsigned long long)'
'operator*(float, int)'
'operator*(float, unsigned int)'
'operator*(float, long)'
'operator*(float, unsigned long)'
'operator*(float, float)'
'operator*(float, double)'
'operator*(float, long double)'
'operator*(float, long long)'
Test.cp line 12       float f = 1.0f * a;

Is this example really legal? It was my understanding that all candidates from 12.5 [over.built] participate in overload resolution.

Daveed Vandevoorde: I believe the EDG-based compiler is right. Note that the built-in operator* requires "usual arithmetic conversions" (see 7.6.5 [expr.mul] paragraph 2 and Clause 7 [expr] paragaph 9). This means that there is no candidate taking (float, double) arguments: Only (float, float) or (double, double).

Since your first argument is of type float, the (float, float) case is preferred over the (double, double) case (the latter would require a floating-point promotion).

Stave Adamczyk: Daveed's statement is wrong; as Andreas says, the prototypes in 12.5 [over.built] paragraph 12 have pairs of types, not the same type twice. However, the list of possibilities considered in Andreas' message is wrong also: 12.5 [over.built] paragraph 12 calls for pairs of promoted arithmetic types, and float is not a promoted type (it promotes to double -- see 7.3.8 [conv.fpprom]).

Nevertheless, the example is ambiguous. Let's look at the overload resolution costs. The right operand is always going to have a user-defined-conversion cost (the template conversion function will convert directly to the const version of the second parameter of the prototype). The left operand is always going to have a promotion (float --> double) or a standard conversion (anything else). So the cases with promotions are better than the others. However, there are several of those cases, with second parameters of type int, unsigned int, long, unsigned long, double, and long double, and all of those are equally good. Therefore the example is ambiguous.

Here's a reduced version that should be equivalent:

  struct A {
    template <typename T> operator T() const;
  } a;
  void f(double, int);
  void f(double, unsigned int);
  void f(double, long);
  void f(double, unsigned long);
  void f(double, double);
  void f(double, long double);
  int main() {
    f(1.0f, a);  // Ambiguous
  }

Personally, I think this is evidence that 12.5 [over.built] doesn't really do quite what it should. But the standard is clear, if possibly flawed.

Andreas Hommel: You are right, "float" is not a promoted arithmetic type, this is a bug in our compiler.

However, the usual arithmetic conversions (Clause 7 [expr] paragraph 9) do not promote the floating point types, so

  float operator+(float, float);
is a legal built-in operator function, so I wonder if it shouldn't be included in the candidate list.

Steve Adamczyk: Hmm, the definition of the term in 12.5 [over.built] paragraph 2 is highly ambiguous:

Similarly, the term promoted arithmetic type refers to promoted integral types plus floating types.
I can't tell if that's "promoted integral types plus (all) floating types" or "promoted integral types plus (promoted) floating types". I thought the latter was intended, but indeed the usual arithmetic conversions could give you "float + float", so it makes sense that float would be one of the possibilities. We should discuss this to make sure everyone has the same interpretation.

Proposed Resolution (October 2003):

Change the second sentence of 13.6 paragraph 2 as follows:

Similarly, the term promoted arithmetic type refers to promoted integral types plus floating types floating types plus promoted integral types.