4

The following code compiles with MSVC (/std:c++latest) and GCC (-std=c++2c), but not Clang (-std=c++2c). Compiler Explorer demo here.

#include <type_traits>

struct A
{
    A(int set_value) : _value{set_value}
    {
    }

    template <typename other>
        requires std::is_arithmetic<other>::value
    operator other() const
    {
        return other(_value);
    }

    A operator*(const A &rhs) const
    {
        return _value * rhs._value;
    }

    int _value = 0;
};

int main()
{
    (void)(A(3) * -1);
}

Which compiler(s) are handling this code correctly? What is the preferred way to get this working under Clang?

Clang's output from Compiler Explorer:

<source>:26:17: error: use of overloaded operator '*' is ambiguous (with operand types 'A' and 'int')
   26 |     (void)(A(3) * -1);
      |            ~~~~ ^ ~~
<source>:16:7: note: candidate function
   16 |     A operator*(const A &rhs) const
      |       ^
<source>:26:17: note: built-in candidate operator*(float, int)
   26 |     (void)(A(3) * -1);
      |                 ^
<source>:26:17: note: built-in candidate operator*(double, int)
<source>:26:17: note: built-in candidate operator*(long double, int)
<source>:26:17: note: built-in candidate operator*(int, int)
[...many similar lines omitted...]
<source>:26:17: note: built-in candidate operator*(unsigned long long, unsigned long)
<source>:26:17: note: built-in candidate operator*(unsigned long long, unsigned long long)
<source>:26:17: note: built-in candidate operator*(unsigned long long, unsigned __int128)
1 error generated.
Compiler returned: 1

(I am trying to create a strong_alias wrapper type which is interchangeable with primitive types, but not other strong_aliases.)

2
  • I would make conversions explicit: explicit constexpr operator other() const; and explicit constexpr strong_alias(other const&); Commented May 14, 2024 at 8:21
  • Moreover, I would use CRTP so I don't need two identifiers for the same type. However, it would require a forwarding constructor declaration. You can also make the class an aggregate. Commented May 14, 2024 at 8:26

1 Answer 1

3
int main()
{
    (void)(A(3) * -1);
}

There are several different ways in which this * can be interpreted. It can be A::operator*, which is callable through the implicit conversion of the second operand from int to A, or it can be a built-in arithmetic operator*, which is callable through the implicit conversion of the first operand from A to an arithmetic type (possibly with the second operand also undergoing an implicit conversion to a different arithmetic type). Clang helpfully prints out all the candidates.

The reason why it's ambiguous is that when calling operator* we get an exact match with the first parameter type and a user-defined conversion with the second parameter type. When calling the built-in operator*(int, int), we get a user-defined conversion with the first parameter type and an exact match with the second parameter type. So neither of these candidates is better than the other.

Clang is correct. As for how to get your original code to compile, since the issue is caused by two candidates that are each viable through a different implicit conversion, your options are basically:

  1. Provide your own overload that is better than all the currently existing candidates, for example, an operator* that has const strong_alias& as its first parameter type and an arithmetic type as its second parameter type, or
  2. Change the converting constructor of A to be explicit, or
  3. Change the conversion function (A::operator other) to be explicit, or
  4. Any combination of the above

I can't tell you which option is correct for you. It depends on the API that you want your type to provide.

The reason why GCC and MSVC don't think it's ambiguous may have to do with the fact that they use a different algorithm for figuring out which built-in operators are candidates. In this particular case they simply give the wrong answer, but more generally the standard does not give enough guidance on how to narrow down a set of relevant built-in candidates in other situations that may potentially involve an unbounded universe of initial candidates. See CWG2844.

Sign up to request clarification or add additional context in comments.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.