Boost logo

Boost :

Subject: [boost] [operators] What is the correct overload set for a binary operator?
From: Daniel Frey (d.frey_at_[hidden])
Date: 2013-04-28 06:15:47


Hi,

after some more experiments, I convinced myself that I should explore the option of returning an rvalue (not an rvalue reference) in all cases when implementing operator+ based on operator+= (as an example). But I wonder what the most efficient overload set would look like. With this post I'll concentrate on same-type operations (T+T).

Given a class T with operator+= like this:

   T& T::operator+=( const T& );
   T& T::operator+=( T&& ); // if useful the class provides it and it should be used if applicable

I think the following is currently the best overload set for operators:

    T operator+( T lhs, const T& rhs )
    {
        lhs += rhs;
        return lhs; // no std::move needed
    }

    T operator+( T&& lhs, T&& rhs )
    {
        lhs += std::move( rhs );
        return std::move( lhs );
    }

    T operator+( const T& lhs, T&& rhs )
    {
#ifdef COMMUTATIVE
        rhs += lhs;
        return std::move( rhs );
#else
        T nrv( lhs );
        nrv += std::move( rhs );
        return nrv;
#endif
    }

I tested this with the following expressions with both COMMUTATIVE defined and not defined:

    T a, b, c; // initialized to something

    T r1 = a + b + c;
    T r2 = a + ( b + c );
    T r3 = ( a + b ) + ( b + c );

and with both GCC 4.7.3 and Clang 3.2. Could someone please test this with VC++? (complete code below)

Generally: Any thoughts? Examples of expressions where and how it could be improved? Theoretical improvements are also welcome, but please mark them as such and always test your code to prevent pessimizations in practice!

One remarks about the proposed full version of operators2.hpp: Andrew, I really appreciate your work and enthusiasm, but please understand that IMHO this needs to be done step-by-step and much more slowly than you probably hope for. Let's figure out the proper overload set for T+T first, then T+U/U+T (again both commutative and non-commutative), noexpect-specifier for all overloads, etc. After that, we need to decide on whether or not to improve the existing operator library or to provide an independent V2. This might include rethinking the grouped templates. Using Boost.Move might help with the implementation or not, depending on what overload set we want/need. It all needs tests and documentation and it will be quite some work… hope you and others are still with me on this :)

Best regards, Daniel

Here's the complete test code I used:

#include <iostream>
#include <utility>

struct MyClass
{
  MyClass() { std::cout << "MyClass::MyClass()" << std::endl; }
  MyClass( const MyClass& ) { std::cout << "MyClass::MyClass( const MyClass& ) COPY" << std::endl; }
  MyClass( MyClass&& ) { std::cout << "MyClass::MyClass( MyClass&& ) move" << std::endl; }

  ~MyClass() { std::cout << "MyClass::~MyClass()" << std::endl; }

  MyClass& operator+=( const MyClass& ) { std::cout << "MyClass::operator+=( const MyClass& ) +=" << std::endl; return *this; }
  MyClass& operator+=( MyClass&& ) { std::cout << "MyClass::operator+=( MyClass&& ) += &&" << std::endl; return *this; }
};

MyClass operator+( MyClass lhs, const MyClass& rhs )
{
  lhs += rhs;
  return lhs;
}

MyClass operator+( MyClass&& lhs, MyClass&& rhs )
{
  lhs += std::move( rhs );
  return std::move( lhs );
}

// #define COMMUTATIVE

MyClass operator+( const MyClass& lhs, MyClass&& rhs )
{
#ifdef COMMUTATIVE
  rhs += lhs;
  return std::move( rhs );
#else
  MyClass nrv( lhs );
  nrv += std::move( rhs );
  return nrv;
#endif
}

int main()
{
  MyClass a, b, c;

  std::cout << "--- r1" << std::endl;
  MyClass r1 = a + b + c;
  std::cout << "--- r2" << std::endl;
  MyClass r2 = a + ( b + c );
  std::cout << "--- r3" << std::endl;
  MyClass r3 = ( a + b ) + ( b + c );
  std::cout << "--- done" << std::endl;
}


Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk