user-defined conversion operators precedence, compiles in g++ but not clang++

五迷三道 提交于 2020-01-04 09:15:14

问题


I have the following code, which is a wrapper for POD into a template class Foo<T>, where T is the wrapped type (can be int, double etc). I define a templated conversion operator, and also the friend addition operator+, see code below. In the last line of main(), I define Foo<int> a = 10 then compute

cout << 2.1 + a << endl; // outputs 12

What is the rule here? It looks like the expression is being translated as

operator+(2.1, a)

which then becomes operator+(Foo<int>(2.1), a). Why doesn't the compiler try to first convert a into a double then perform the addition? I.e. why don't we have the expression evaluated as

2.1 + a.operator double()

Thanks!

PS: Just realized that clang++ fails to compile the code, saying that overloaded call to operator+ is ambiguous. However g++4.9 compiles it without problems, even with all warning flags turned on.

Code snippet below:

#include <iostream>
using namespace std;

template <typename T>
class Foo // wrapper class for a POD
{
    T val_; // this is the wrapped value
public:
    Foo(T val = {}): val_(val) {};

    template<typename S> // conversion operator
    operator S ()
    {
        std::cout << "Calling conversion operator" << std::endl;
        return val_;
    }

    // the += operator
    Foo& operator+=(const Foo& rhs)
    {
        val_ += rhs.val_; 
        return *this;
    }

    // the + operator
    friend Foo operator+(Foo lhs, const Foo& rhs)
    {
        cout << "Calling operator+" << endl;
        return lhs += rhs;
    }

    // stream operator
    friend std::ostream &operator<<(std::ostream &os, const Foo &rhs)
    {
        return os << rhs.val_;
    }
};

int main()
{
    Foo<int> a = 10;

    // operator+(2.1, a), why not
    // 2.1 + a. operator int() ?
    cout << 2.1 + a << endl; // outputs 12
}

回答1:


Let's start from §13.3.1.2 [over.match.oper]/p2-3:

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.

[...]

for a binary operator @ with a left operand of a type whose cv-unqualified version is T1 and a right operand of a type whose cv-unqualified version is T2, three sets of candidate functions, designated member candidates, nonmember candidates and built-in candidates, are constructed as follows:

  • If T1 is a complete class type or a class currently being defined, the set of member candidates is the result of the qualified lookup of T1::operator@ (13.3.1.1.1); otherwise, the set of member candidates is empty.
  • The set of non-member candidates is the result of the unqualified lookup of operator@ in the context of the expression according to the usual rules for name lookup in unqualified function calls (3.4.2) except that all member functions are ignored. [...]
  • 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 13.6 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 13.3.3.1, and
    • do not have the same parameter-type-list as any non-member candidate that is not a function template specialization.

So, given the expression 2.1 + a, let's construct the candidate sets:

  • T1 is double, not a class type, so the set of member candidates is empty.
  • The non-member candidate set consists of:

    Foo<int> operator+(Foo<int> lhs, const Foo<int>& rhs);
    

    (plus lots of other overloads for different instantiations of Foo that's obviously a worse match than this one.)

  • The built-in candidate set consists of a long list of functions you can see in clang's output on your code, specified in §13.6 [over.built]/p12:

    For every pair of promoted arithmetic types L and R, there exist candidate operator functions of the form [...] LR operator+(L , R ); [...] where LR is the result of the usual arithmetic conversions between types L and R.

Overload resolution can succeed only if a unique best match can be found among this pile of candidates.

First, note that of the numerous possible built-in operators below, none can possibly be the unique best match, because Foo<int> is convertible to every possible type of the right operand:

operator+(double, unsigned long long)
operator+(double, unsigned long)
operator+(double, unsigned int)
operator+(double, __int128)
operator+(double, long long)
operator+(double, long)
operator+(double, float)
operator+(double, double)
operator+(double, long double)
operator+(double, int)

(I only listed ones whose first argument is of type double, since that's the type of the first argument, and hence the other built-ins can't possibly be better than any of those.)

Thus, overload resolution can succeed if and only if your overload of operator + is a better match than every one of them. Without loss of generality, we consider the following two functions:

    operator+(double, int);  // built-in
    Foo<int> operator+(Foo<int> lhs, const Foo<int>& rhs); // overload

given an argument list of (double, Foo<int>). For the first candidate, the first argument is an exact match, the second one requires a user-defined conversion. For the second candidate, the first argument requires a user-defined conversion, and the second one is an exact match.

We thus have a criss-cross situation, which means that neither candidate function is better than the other. (The first requirement of one function F1 being better than the other F2 is that for each argument the conversion required for F1 is not worse than F2 - §13.3.3 [over.match.best]/p2.)

As a result, there's no unique best overload, overload resolution fails, and the program is ill-formed. Clang is correct in rejecting this code, and g++ is broken in failing to reject it.




回答2:


I don't have the manual handy, but C++ prefers to implicitly typecast to objects over typecasting from objects. In other words, it'll interpret double + Foo<int> as Foo<int>(double) + Foo<int> if Foo<int> has a constructor that can take a double. ("Can take a double" allows implicit typecasting of the double to other things like int unless the constructor in question is declared explicit.)

If Foo<int> doesn't have a suitable constructor, only then would it consider calling Foo<int>::operator double() to degrade the object to a double... and I'm not even sure the language will even try that implicitly!

If you really want to make double + Foo<int> convert the Foo<int> to a double first, then add, you'll need to write:

double operator +(double a, const Foo<int>& b)
{
    return a + double(b);
}

or some kind of template equivalent. No friend declaration needed so long as Foo<int>::operator double() exists.



来源:https://stackoverflow.com/questions/25100855/user-defined-conversion-operators-precedence-compiles-in-g-but-not-clang

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!