User-defined operator precedence compiles in g ++ but not clang ++

I have the following code, which is the wrapper for the POD in the template class Foo<T> , where T is the wrapped type (maybe int , double , etc.). I define a template conversion operator, as well as adding a friend operator+ , see the code below. In the last line of main() , I define Foo<int> a = 10 , then calculate

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

What is the rule here? It looks like the expression is expressed as

 operator+(2.1, a) 

which then becomes operator+(Foo<int>(2.1), a) . Why doesn't the compiler try to convert a to double , and then do the addition? That is, why don't we evaluate the expression as

 2.1 + a.operator double() 

Thanks!

PS: I just realized that clang++ could not compile the code, stating that the overloaded operator+ call was ambiguous. However, g++4.9 compiles it without problems, even when all warning flags are turned on.

The following is a snippet of code:

 #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 } 
+2
c ++ c ++ 11 operator-overloading templates
source share
2 answers

Let's start with ยง13.3.1.2 [over.match.oper] / p2-3:

If any operand has a type that is a class or enumeration, a user-defined operator function may be declared that implements this operator or a user-defined conversion may be necessary to convert the operand to a type that is suitable for the built-in operator. In this case, overload resolution is used to determine which operator function or built-in operator to call the operator.

[...]

for the binary operator @ with the left operand of type cv-unqualified version T1 and the right operand of type cv-unqualified version T2 , three sets of candidate functions, assigned candidate candidates, non-member candidates, and built-in candidates are constructed as follows:

  • If T1 is the full type of class or the class that is currently being defined, the set of candidate candidates is the result of a qualified search T1:: operator@ (13.3.1.1.1); otherwise, the set of candidate candidates is empty.
  • The set of non-member candidates is the result of an unqualified search operator@ in the context of an expression in accordance with the usual rules for finding names 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 set of candidates is empty. For all other operators, the built-in candidates include all the functions of the candidate operator defined in 13.6, which, compared with this operator,
    • have the same operator name and
    • accept the same number of operands and
    • accept the types of operands to which a given operand or operands can be converted in accordance with 13.3.3.1 and
    • do not have the same parameter list as any non-member candidate that is not a specialized function.

So, given the expression 2.1 + a , we construct sets of candidates:

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

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

    (plus many other overloads for different instances of Foo , which are obviously worse than this one.)

  • The built-in candidate set consists of a long list of functions that you can see in the clang output on your code specified in ยง13.6 [over.built] / p12:

    For each pair of advanced arithmetic types L and R there is a candidate operator operator of the form [...] LR operator+(L , R ); [...] where LR is the result of ordinary arithmetic conversions between types L and R

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

First, note that out of the many possible built-in statements below, none of them can be the only best match, because Foo<int> converted to all possible types of the correct 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 those whose first argument is of type double , since the type of the first argument and, therefore, other built-in modules cannot be better than any of them.)

Thus, overload resolution can be successful if and only if your operator + overload is better than each one. 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 

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

Thus, we have a cross-cutting situation, which means that not one of the candidates is better than the other. (The first requirement of one function F1 is better than another F2, is that for each argument the conversion required for F1 is no worse than F2 - ยง13.3.3 [over.match.best] / p2.)

As a result, there is no single best overload, the overload error failed, and the program is poorly formed. Clang correctly rejects this code, and g ++ does not work without abandoning it.

+3
source share

I don't have manual control, but C ++ prefers to implicitly assign objects to objects compared to objects. In other words, it will interpret double + Foo<int> as Foo<int>(double) + Foo<int> if Foo<int> has a constructor that can accept double . ("Can take a double value" allows for implicit casting of double types to other things, such as int , unless this constructor is explicit declared.)

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

If you really want to do double + Foo<int> first convert Foo<int> to double , and then add, you will need to write:

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

or some equivalent template. There is no friend declaration if Foo<int>::operator double() exists.

+1
source share

All Articles