Is operator lookup different for temporary/anonymous objects?

71 Views Asked by At

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.

1

There are 1 best solutions below

0
On

The 3u literal converts implicitly into a Wrapper and thus both overloads of operator+= are possible candidates in the line the compiler flags to you.

One simple solution is to mark both Wrapper constructors with explicit. Then, they will never allow implicit conversion to a Wrapper but will always require you to type out the conversion as you did in the first line of main.