-1

I am trying to use custom operators in C++ for a project I am working on. This project uses the ROCm/HIP stack (so, under the hood, the clang compiler).

Here's the error message:

src/zlatrd.cpp:359:32: error: use of overloaded operator '*' is ambiguous (with operand types 'magmaDoubleComplex' (aka 'hip_complex_number<double>') and 'float')
                alpha = tau[i] * -0.5f * value;
                        ~~~~~~ ^ ~~~~~
./include/magma_operators.h:190:1: note: candidate function
operator * (const magmaDoubleComplex a, const double s)
^
./include/magma_operators.h:183:1: note: candidate function
operator * (const magmaDoubleComplex a, const magmaDoubleComplex b)
^
./include/magma_operators.h:437:1: note: candidate function
operator * (const magmaFloatComplex a, const float s)
^
./include/magma_operators.h:430:1: note: candidate function
operator * (const magmaFloatComplex a, const magmaFloatComplex b)
^

It seems to me that it is not ambiguous; it should select the third candidate function, as the argument is a float.

Here is the type definition for the hip_complex_number template:

template <typename T>
struct hip_complex_number
{
    T x, y;

    template <typename U>
    hip_complex_number(U a, U b)
        : x(a)
        , y(b)
    {
    }
    template <typename U>
    hip_complex_number(U a)
        : x(a)
        , y(0)
    {
    }
    hip_complex_number()
        : x(0)
        , y(0)
    {
    }
};

I notice it has an implicit constructor that will convert a float, but I assumed that given a candidate function that matches the type exactly (not including the const modifier), that it would obviously select that function overload over those which require an implicit cast.

EDIT: Also, I know that by default C/C++ convert from float/double to each other if the function is defined in that way, so 'matches the type exactly' was definitely not the right wording.

Can someone explain why C++ thinks this is ambiguous?

EDIT: People have asked for the definition of magmaFloatComplex, which is hip_complex_number<float>

Cade Brown
  • 165
  • 2
  • 8
  • 1
    Related: explicit keyword https://stackoverflow.com/questions/121162/what-does-the-explicit-keyword-mean – Michael Chourdakis Jan 16 '20 at 17:38
  • 4
    The message says that `tau[i]` ss a `magmaDoubleComplex`, so the third function isn't an exact match. – Toby Speight Jan 16 '20 at 17:39
  • 4
    What is `magmaFloatComplex`? If it isn't `hip_complex_number` as well, it will require a conversion just as all the other overloads. – walnut Jan 16 '20 at 17:41
  • Unrelated: You should use `const magnaFloatComplex &a` rather than by value – ChrisMM Jan 16 '20 at 17:41
  • 2
    Create a [mcve] – eerorika Jan 16 '20 at 17:42
  • 1
    @Chris, perhaps so, but it's normal to take the first argument by value, since we'll need a new object anyway: `T operator*(T a, const T& b) { return a *= b; }` – Toby Speight Jan 16 '20 at 17:44
  • @TobySpeight Why assign to a local in a statement that returns? (a local won't be used after return) – eerorika Jan 16 '20 at 17:45
  • 1
    @eerorika The local becomes the return value (NRVO). – Raymond Chen Jan 16 '20 at 17:50
  • @RaymondChen not NRVO in this example though – Oktalist Jan 16 '20 at 17:54
  • While the question still doesn't have a [repro], I think it now has sufficient information to explain why the overload resolution is ambiguous. I have voted to reopen it. – walnut Jan 16 '20 at 17:54
  • @RaymondChen I don't think arguments can be NRVO'd in practice. – eerorika Jan 16 '20 at 17:54
  • @walnut magma types are standard types for this toolchain, it's not user-defined types. But reason of error is obvious - there is no candidate for double complex and float. Second argument should be explicitly cast to `double` – Swift - Friday Pie Jan 16 '20 at 18:08
  • @Swift-FridayPie (Not knowing anything about what `hip_complex_number` is outside of what the question says.) It may be obvious what is missing, but I don't think it is trivial why overload 1 and 3 are ambiguous. – walnut Jan 16 '20 at 18:11
  • @TobySpeight, true, but then it shouldn't be `const T a` as it is in the question, and instead just `T a` like you mentioned. – ChrisMM Jan 16 '20 at 18:14
  • 1
    @OP: Did you simply misread the overload parameters? (The third overload doesn't match *exactly*.) If so, I would suggest deleting the question. Otherwise, a clarification in the question would improve chances of getting the question reopened and answered. – walnut Jan 16 '20 at 18:18

1 Answers1

1

Please note that I don't know anything about the library that these types are from. I will explain the ambiguity purely based on the information in the question.


The first and third overload are ambiguous.

In the overload operator * (const magmaDoubleComplex a, const double s) a floating-point promotion from float to double is required in the second argument.

In the overload operator * (const magmaFloatComplex a, const float s) a user-defined conversion to an unrelated type from magmaDoubleComplex to magmaFloatComplex is required. This conversion is possible, because of the non-explicit converting constructor

template <typename U>
hip_complex_number(U a)

The corresponding other parameters don't need any conversion aside from potentially lvalue-to-rvalue conversions or user-defined conversion to the same type, which are considered exact match.

Exact match is better than either user-defined conversion to an unrelated type or floating-point promotion, meaning that each overload has one parameter that is better than the other one's and one that is worse than the other one's.

Overload resolution is ambiguous if not at least one overload has all parameters not worse than all other overload's parameters. Therefore the two mentioned overloads are ambiguous here.


The second overload has exact match in the first argument and requires a user-defined conversion to unrelated type in the second argument, which is again possible because of the converting constructor mentioned above. However the floating-point promotion of the first overload is considered better than a user-defined conversion to unrelated type and therefore the second overload looses against the first one in overload resolution, but would be ambiguous with the third one as well.

The fourth overload is worse than all the others, because it requires user-defined conversions to unrelated types in both parameters.


Note that if overload 3 would be selected as expect in your question, it would result in an error, because the converting constructor chosen for magmaFloatComplex will try to initialize the x member which is of type float with a magmaDoubleComplex, which (at least based on your shown code) doesn't have a conversion operator to float.

walnut
  • 21,629
  • 4
  • 23
  • 59
  • Thank you! I did not know C++ would treat a constructor with a single argument as a conversion. Since it tried to do that, it can be treated as an implicit `float -> complex` conversion. – Cade Brown Jan 17 '20 at 04:20
  • @CadeBrown Yes that is also possible in the second and fourth overload, but both of them are worse candidates than the first overload. It is only the third overload with the implicit `magmaDoubleComplex` -> `magmaFloatComplex` conversion that is making the overload resolution ambiguous, see my expanded answer. In any case this can be resolved by adding an overload taking `magmaDoubleComplex` and `double` (which would be a better match than all the other overloads) or by marking the converting constructor `explicit`, which forbids its use as implicit conversion. – walnut Jan 17 '20 at 08:22