Conversion operator: gcc vs clang
Asked Answered
U

1

8

Consider the following code (https://godbolt.org/z/s17aoczj6):

template<class T>
class Wrapper {
    public:
    explicit Wrapper(T t): _value(t) {}

    template<class S = T>
    operator T() { return _value; }
    private:
    T _value;
};

auto main() -> int
{
    auto i = int{0};
    auto x = Wrapper<int>(i);
    return x + i;
}

It compiles with clang, but not with gcc (all versions). It works in gcc when removing the template<class S = T>. Is this code ill-formed or is one compiler wrong?

The error is in gcc is error: no match for 'operator+' (operand types are 'Wrapper<int>' and 'int') return x + i;.

I want a conversion to T. The template is not necessary in this example, but in a non-minimal example I would like to use SFINAE and therefore need a template here.

Undersecretary answered 30/11, 2021 at 10:45 Comment(9)
you might consider to add the language-lawyer tag. I didn't do it, because it slightly changes the scope of the question.Waterborne
why you need this extra template? Without it it works everywhere godbolt.org/z/53vq5Ycd7Lancer
@MarekR it might be needed for SFINAE to select between two different implementations of the operatorWaterborne
It seems that you need your operator to perform a conversion if possible. So it would be operator S(), not operator T(). Since it would be not possible to deduce S for a compiler, it might be the reason for the error. Please, provide the error textPhalangeal
There is no conversion in the constructor. It is in the x+i operation.Undersecretary
In template<class S = T> operator T(), does it matter that the conversion is a template at all? Does it work without the template? In any case, when the template param is S, it should really be operator S(), too. Also, consider making it const and not using ALL_UPPERCASE identifiers that could be confused with macros.Mignonmignonette
Clarified the question (but it alreay was in the comments). The template matters and it should be operator T. What do you mean by ALL_UPPERCASE here?Undersecretary
In your example typename S is not possible to be deduced from the context - in your case it can only be deduced to T. So what kind of SFINAE do you expect here, if S always defaults to T and can never ever be deduced or substituted to something else?Phalangeal
What would be the meaning of Wrapper<int>::operator T<double>(), by the way?Rathe
A
7

When you have x + i, since x is of class type, overload resolution kicks in:

The specific details from the standard ([over.match.oper]p2)

If either operand has a type that is a class or an enumeration, a user-defined operator function might be declared that implements this operator or a user-defined conversion can be necessary to convert the operand to a type that is appropriate for a built-in operator. In this case, overload resolution is used to determine which operator function or built-in operator is to be invoked to implement the operator.

The built-in candidates are defined in paragraph 3.3:

For the operator ,, the unary operator &, or the operator ->, the built-in candidates set is empty. For all other operators, the built-in candidates include all of the candidate operator functions defined in [over.built] that, compared to the given operator,

  • have the same operator name, and
  • accept the same number of operands, and
  • accept operand types to which the given operand or operands can be converted according to [over.best.ics], and
  • do not have the same parameter-type-list as any non-member candidate that is not a function template specialization.

The built-in candidate functions could consist of, according to [over.built]p13:

For every pair of types L and R, where each of L and R is a floating-point or promoted integral type, there exist candidate operator functions of the form

LR      operator*(L, R);
...
LR      operator+(L, R);
...
bool    operator>=(L, R);

where LR is the result of the usual arithmetic conversions ([expr.arith.conv]) between types L and R.

So there is a built-in function int operator+(int, int).

As to what possible implicit conversion sequences there are:

[over.best.ics]p3:

A well-formed implicit conversion sequence is one of the following forms:

  • a standard conversion sequence,
  • a user-defined conversion sequence, or
  • an ellipsis conversion sequence.

And here a user-defined conversion sequence is used, defined by [over.ics.user]:

A user-defined conversion sequence consists of an initial standard conversion sequence followed by a user-defined conversion ([class.conv]) followed by a second standard conversion sequence.

(Here, both standard conversion sequences are empty, and your user defined conversion to an int can be used)

So when checking if int operator+(int, int) is a built-in candidate, it is since there exists a conversion between your class type and int.


As for the actual overload resolution, from [over.match.oper]:

  1. The set of candidate functions for overload resolution for some operator @ is the union of the member candidates, the non-member candidates, the built-in candidates, and the rewritten candidates for that operator @.
  2. The argument list contains all of the operands of the operator. The best function from the set of candidate functions is selected according to [over.match.viable] and [over.match.best].

And int operator+(int, int) is obviously the best match, since it requires no conversion for the second argument and only a user defined conversion for the first, so it beats other candidates like long operator+(long, int) and long operator+(int, long)


You can see the problem that the built-in candidate set is empty, since the GCC error reports that there are no viable candidates. If you instead had:

auto add(int a, int b) -> int
{
    return a + b;
}

auto main() -> int
{
    auto i = int{0};
    auto x = Wrapper<int>(i);
    return add(x, i);
}

it now compiles fine with GCC since ::add(int, int) is considered a candidate, even though it should be no different from the built-in operator int operator+(int, int).

If you instead had:

    template<class S = T>
    operator S() { return _value; }  // Can convert to any type

Clang now has the error:

<source>:16:14: error: use of overloaded operator '+' is ambiguous (with operand types 'Wrapper<int>' and 'int')
    return x + i;
           ~ ^ ~
<source>:16:14: note: built-in candidate operator+(float, int)
<source>:16:14: note: built-in candidate operator+(double, int)
<source>:16:14: note: built-in candidate operator+(long double, int)
<source>:16:14: note: built-in candidate operator+(__float128, int)
<source>:16:14: note: built-in candidate operator+(int, int)
<source>:16:14: note: built-in candidate operator+(long, int)
<source>:16:14: note: built-in candidate operator+(long long, int)
<source>:16:14: note: built-in candidate operator+(__int128, int)
<source>:16:14: note: built-in candidate operator+(unsigned int, int)
<source>:16:14: note: built-in candidate operator+(unsigned long, int)
<source>:16:14: note: built-in candidate operator+(unsigned long long, int)
<source>:16:14: note: built-in candidate operator+(unsigned __int128, int)

(Note this error message excludes conversions of the second argument, but since these will never be chosen, they are probably not considered as an optimisation)

And GCC still says there are no candidates at all, even though all of these built-in candidates exist.

Achlamydeous answered 30/11, 2021 at 11:34 Comment(4)
So in conclusion, GCC should consider this candidate but it does not (therefore it is a bug)?Undersecretary
@Undersecretary Yes. I've also found the exact same problem occurring here: https://mcmap.net/q/1025218/-user-defined-conversion-operator-template-and-built-in-operators-no-match-for-operator and a similar problem here: https://mcmap.net/q/1469223/-user-defined-conversion-operators-precedence-compiles-in-g-but-not-clangAchlamydeous
Interesting! But I have not found a bug report for this, the code is quite simple and I have an example where I do not know any other soltution than a templated conversion operator. Should this be reported?Undersecretary
A similar problem here: gcc.gnu.org/bugzilla/show_bug.cgi?id=85250 It seems that gcc struggles to consider a templated operator T when T is dependent (shorter example: godbolt.org/z/79rjTovn9)Achlamydeous

© 2022 - 2024 — McMap. All rights reserved.