问题
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 isT1
and a right operand of a type whose cv-unqualified version isT2
, 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
isdouble
, 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
andR
, there exist candidate operator functions of the form [...]LR operator+(L , R );
[...] whereLR
is the result of the usual arithmetic conversions between typesL
andR
.
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