Why does cannonical implementation of overloading binary arithmatic operator in C++ pass first arguement by value?

According to this cppreference page, passing the first argument of an overloaded binary operator by value somehow optimizes expressions like a + b + c. The relevant code snippet from the linked page is as follows:

class X
{
 public:
  X& operator+=(const X& rhs)         // compound assignment (does not need to be a member,
  {                                   // but often is, to modify the private members)
    /* addition of rhs to *this takes place here */
    return *this;                     // return the result by reference
  }

  // friends defined inside class body are inline and are hidden from non-ADL lookup
  friend X operator+(X lhs,           // passing lhs by value helps optimize chained a+b+c
                     const X& rhs)    // otherwise, both parameters may be const references
  {
    lhs += rhs;                       // reuse compound assignment
    return lhs;                       // return the result by value (uses move constructor)
  }
};

I have 2 questions regarding this:

  1. How are chained a + b + c expressions optimized by this?
  2. Assuming X also overrides the copy and move assignment operators and constructors, would a statement like x = a + b produce any copying? Why or why not?

I don't know whether this had that example-writer in mind, but have some explanation. Consider what happens in this code:

X a, b, c;
X d = a + b + c;

Here, first, a + b is evaluated basically as operator+(operator+(a, b), c). Note that operator+(a, b) is an rvalue, and therefore, lhs can, in the outer application of operator+, be initialized by move-constructor.

An alternative how to implement operator+ in terms of operator+= is as follows:

friend X operator+(const X& lhs, const X& rhs) 
{    
   X temp(lhs);
   temp += rhs;
   return temp;
}

Note that you need to create a temporary since you need an object to apply operator+= on. With this solution, both applications of operator+ in operator+(operator+(a, b), c) involves copy-constructor.

Live demo: https://godbolt.org/z/5Dq7jF

Of course, you can add a second version for rvalues as follows:

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

But this requires much more typing than the original value-passing version.


Generally, passing by value is frequently used in situations where you want to unify overloads for lvalues and rvalues; for example, look for unified assignment operator.

no, () isn't an operator so it cannot be overloaded. (this is incorrect, please see eric lippert's comment below) the parentheses are part of c#'s syntax that are used to express a set of arguments that are passed to a method. () simply indicates that the method in question specified no formal parameters and therefore requires no arguments.

what are you trying to do? perhaps if you gave a small example of the problem we would be able to help with a solution.

edit: okay i see what you are getting at now. you could always create a delegate that maps to the method on the instance like this (given that class a defines a method like this: public void foo() { }):

action action = somea.foo;

then you could invoke the delegate with a simple syntax like this:

action();

unfortunately (or not, depending on your preference) this is pretty much as close as c# will let you get to this kind of syntax.

the copy constructor is called because you call by value not by reference. therefore a new object must be instantiated from your current object since all members of the object should have the same value in the returned instance. because otherwise you would be returning the object it self, which would be returning by reference. in this case modifying the reference object would change the original as well. this is generally not a behavior one wants when returning by value.

like lucian said, you cannot bind a temporary object to a non-const reference. the expectance of the compiler is that the object will cease to exist after the expression so it makes no sense to modify it.

to fix your code, remove the temporary (making the argument const& makes no sense in operator *=):

a operator*(a a, const a& b)
{
    return a *= b;
}

it's quite easy, don't panic :)

you have recognized the problem well: it's very similar to the std::cout - std::endl work.

you could do like such, though i'll rename the types, if you don't mind.

struct endmarker {};
extern const endmarker end; // to be defined in a .cpp

class data
{
public:
  data(): m_data(1, "") {}

  // usual operator
  template <class t>
  data& operator<<(const t& input)
  {
    std::ostringstream astream;
    astream << input;
    m_data.back() += astream.str();
  };

  // end of object
  data& operator<<(endmarker) { m_data.push_back(""); }

private:
  std::vector<std::string> m_data;
}; // class data

it works by adding to the current last element by default, and pushing an empty element at the end.

let's see an example:

data data;
data << 1 << "bla" << 2 << end << 3 << "foo" << end;

// data.m_data now is
// ["1bla2", "3foo", ""]

the other solution would be to keep a flag (boolean) to store if a end has been streamed or not, and if it has, creating a new element on the next insertion (and erasing the flag).

it a bit more work on insertion, but you don't have the empty element... your call.


Tags: C++ Operator Overloading