I am trying to understand why named and temporary (anonymous?) objects seem to behave differently when looking up an operator defined in a base class. In the following code I've made a wrapper around a variable of 'mytype' which I want to switch between double and std::complex through a compiler definition. To this wrapper I want to add an operator using a base type (similar to boost::operators). My interest is in the difference between the two (admittedly quite forced) cout lines in 'main'.
#include <iostream>
#include <cmath>
#include <complex>
#ifdef COMPLEX
typedef std::complex<double> mytype;
#else
typedef double mytype;
#endif
template <typename DerivedType, typename integertype>
struct Base
{
friend DerivedType& operator +=(DerivedType& lhs, const integertype& rhs)
{
std::cout << "base += version" << std::endl;
return lhs += mytype(rhs);
}
};
struct Wrapper : public Base<Wrapper, unsigned>
{
Wrapper(const mytype& rhs) : m_value(rhs) {}
Wrapper(const unsigned& rhs) : Wrapper(mytype(rhs)) {}
Wrapper& operator += (const Wrapper& rhs)
{
std::cout << "wrapper version" << std::endl;
m_value += rhs.m_value;
return *this;
}
Wrapper& operator += (const mytype& rhs)
{
std::cout << "wrapper mytype version" << std::endl;
m_value += rhs;
return *this;
}
mytype m_value;
};
int main()
{
std::cout << (Wrapper(2.0) += 3u).m_value << std::endl;
Wrapper t_MyWrapper(2.0);
std::cout << (t_MyWrapper += 3u).m_value << std::endl;
}
if I compile without -DCOMPLEX I get the following output:
wrapper mytype version
5
base += version
wrapper mytype version
5
As far as I can tell, the first output in main ignores the operator+=(Wrapper&, const unsigned&) from the Base. It instead promotes the unsigned to a double (which is preferential to converting to a Wrapper) and calls the operator+=(Wrapper& const double&). The 'named object' however, does call the operator from the base type. Compiling with -DCOMPLEX leads to a compile error:
SimplifiedProblem.cxx: In function ‘int main()’:
SimplifiedProblem.cxx:47:32: error: ambiguous overload for ‘operator+=’ (operand types are ‘Wrapper’ and ‘unsigned int’)
47 | std::cout << (Wrapper(2.0) += 3u).m_value << std::endl;
| ~~~~~~~~~~~~~^~~~~
SimplifiedProblem.cxx:28:14: note: candidate: ‘Wrapper& Wrapper::operator+=(const Wrapper&)’
28 | Wrapper& operator += (const Wrapper& rhs)
| ^~~~~~~~
SimplifiedProblem.cxx:35:14: note: candidate: ‘Wrapper& Wrapper::operator+=(const mytype&)’
35 | Wrapper& operator += (const mytype& rhs)
| ^~~~~~~~
Considering that converting the unsigned to std::complex is not 'better' than to a 'Wrapper' (neither is a scalar type), the compile error makes sense, but I would like to understand why the operator from the base type is not used.
Moving the operator+=(Wrapper&, const unsigned&) to the wrapper directly avoids this problem, and removing the constructor Wrapper(const unsigned&) resolves the ambiguity when -DCOMPLEX. But I would like to understand if the rules for temporary object lookup are different or if something else is causing this behaviour.
The
3u
literal converts implicitly into aWrapper
and thus both overloads ofoperator+=
are possible candidates in the line the compiler flags to you.One simple solution is to mark both
Wrapper
constructors withexplicit
. Then, they will never allow implicit conversion to aWrapper
but will always require you to type out the conversion as you did in the first line ofmain
.