Why can some operators only be overloaded as member functions, other as friend functions and the rest of them as both?

11,716

Solution 1

The question lists three classes of operators. Putting them together on a list helps, I think, with understanding why a few operators are restricted in where they can be overloaded:

  1. Operators which have to be overloaded as members. These are fairly few:

    1. The assignment operator=(). Allowing non-member assignments seems to open the door for operators hijacking assignments, e.g., by overloading for different versions of const qualifications. Given that assignment operators are rather fundamental that seems to be undesirable.
    2. The function call operator()(). The function call and overloading rules are sufficiently complicated as is. It seems ill-advised to complicate the rules further by allowing non-member function call operators.
    3. The subscript operator[](). Using interesting index types it seems that could interfere with accesses to operators. Although there is little danger of hijacking overloads, there doesn't seem to be much gain but interesting potential to write highly non-obvious code.
    4. The class member access operator->(). Off-hand I can't see any bad abuse of overloading this operator a non-member. On the other hand, I also can't see any. Also, the class member access operator has rather special rules and playing with potential overloads interfering with these seems an unnecessary complication.

    Although it is conceivable to overload each of these members are a non-member (especially the subscript operator which is works on arrays/pointers and these can be on either side of the call) it seems surprising if, e.g., an assignment could be hijacked by a non-member overload which which is a better match than one of the member assignments. These operators are also rather asymmetric: you generally wouldn't want to support conversion on both sides of an expression involving these operators.

    That said, e.g., for a lambda expression library it would be nice if it were possible to overload all of these operators and I don't think there is an inherent technical reason to preventing these operators from being overloadable.

  2. Operators which have to be overloaded as non-member functions.

    1. The user-defined literal operator"" name()

    This operator is somewhat of an odd-ball and, arguably not really really an operator. In any case, there is no object to call this member on for which members could be defined: the left argument of user-defined literals are always built-in types.

  3. Not mentioned in the question but there are also operator which can't be overloaded at all:

    1. The member selector .
    2. The pointer-to-member object access operator .*
    3. The scope operator ::
    4. The ternary operator ?:

    These four operators were considered to be too fundamental to be meddled with at all. Although there was a proposal to allow overloading operator.() at some point there isn't strong support doing so (the main use case would be smart references). Although there are certainly some contexts imaginable where it would be nice to overload these operators, too.

  4. Operators which can be overloaded either as members or as non-members. This is the bulk of the operators:

    1. The pre- and post-increment/-decrement operator++(), operator--(), operator++(int), operator--(int)
    2. The [unary] dereference operator*()
    3. The [unary] address-of operator&()
    4. The [unary] signs operator+(), operator-()
    5. The logical negation operator!() (or operator not())
    6. The bitwise inversion operator~() (or operator compl())
    7. The comparisons operator==(), operator!=(), operator<(), operator>(), operator<=(), and operator>()
    8. The [binary] arithmetic operator+(), operator-(), operator*(), operator/(), operator%()
    9. The [binary] bitwise operator&() (or operator bitand()), operator|() (or operator bit_or()), operator^() (or operator xor())
    10. The bitwise shift operator<<() and operator>>()
    11. The logic operator||() (or operator or()) and operator&&() (or operator and())
    12. The operation/assignment operator@=() (for @ being a suitable operator symbol()
    13. The sequence operator,() (for which overloading actually kills the sequence property!)
    14. The pointer pointer-to-member access operator->*()
    15. The memory management operator new(), operator new[](), operator new[](), and operator delete[]()

    The operators which can be overloaded either as members or as non-members are not as necessary for fundamental object maintenance as the other operators. That is not to say that they are not important. In fact, this list contains a few operators where it is rather questionable whether they should be overloadable (e. g., the address-of operator&() or the operators which normally cause sequencing, i. e., operator,(), operator||(), and operator&&().

Of course, the C++ standard doesn't give a rationale on why things are done the way they are done (and there are also no records of the early days when these decisions where made). The best rationale can probably be found in "Design and Evolution of C++" by Bjarne Stroustrup. I recall that the operators were discussed there but there doesn't seem to be an electronic version available.

Overall, I don't think there are really strong reasons for the restrictions other than potential complication which was mostly not considered worth the effort. I would, however, doubt that the restrictions are likely to be lifted as the interactions with existing software are bound to change the meaning of some program in unpredictable ways.

Solution 2

The rationale is that it would not make sense for them to be non-members, as the thing on the left-hand side of the operator must be a class instance.

For example, assuming a class A

A a1;
..
a1 = 42;

The last statement is really a call like this:

a1.operator=(42);

It would not make sense for the thing on the LHS of the . not to be an instance of A, and so the function must be a member.

Solution 3

Because you can't modify the semantics of primitive types. It wouldn't make sense to define how operator= works on an int, how to deference a pointer, or how an array access works.

Solution 4

Here is one example: When you are overloading the << operator for a class T the signature will be:

std::ostream operator<<(std::ostream& os, T& objT )

where the implementation needs to be

{
//write objT to the os
return os;
}

For the << operator the first argument needs to be the ostream object and the second argument your class T object.

If you try to define operator<< as a member function you will not be allowed to define it as std::ostream operator<<(std::ostream& os, T& objT). This is because binary operator member functions can only take one argument and the invoking object is implicitly passed in as the first argument using this.

If you use the std::ostream operator<<(std::ostream& os) signature as a member function you will actually end up with a member function std::ostream operator<<(this, std::ostream& os) which will not do what you want. Therefore you need a operator that is not a member function and can access member data (if your class T has private data you want to stream, operator<< needs to be a friend of class T).

Share:
11,716

Related videos on Youtube

Yakk - Adam Nevraumont
Author by

Yakk - Adam Nevraumont

template&lt;class R, class...Args&gt; auto Y = [](auto f) { auto action = [](auto f, auto&amp;&amp; action)-&gt;std::function&lt;R(Args...)&gt; { return [f, action](Args&amp;&amp;...args)mutable -&gt;R{ return f( action(std::ref(f), action), std::forward&lt;Args&gt;(args)... ); }; }; return action(f, action); }; Some fun C++ stuff I've done round here: A: Convert a run-time value or type to a compile-time value, also works with lists of types. A: Named operators, does what it says on the tin A: can_apply, a useful trick that helps make traits like "can serialize" or "can convert to string". A: task, a move-only implementation of std::function. Q: Function curry, that does partial binds and the like. A: Me playing around with hana-like metaprogramming, together with the idea that template work best if everything (including templates) is a type. Here is some evil operator abuse, where I manipulate types with +. A: std::function that returns vectors of its own type. Here is a neat question I didn't ask: Q: Does SFINAE actually work with class templates? the answer seems to be "actually, nope, but nobody noticed". An amusing link: http://stackoverflow.com/reputation which will show your reputation history on stack overflow, if you like that kind of thing. ey eir em

Updated on October 08, 2020

Comments

  • Yakk - Adam Nevraumont
    Yakk - Adam Nevraumont over 3 years

    Why can some operators only be overloaded as member functions, other as non-member "free" functions and the rest of them as both?

    What is the rationale behind those?

    How to remember which operators can be overloaded as what (member, free, or both)?

    • gx_
      gx_ over 10 years
      @BROY Your edit is incorrect, a non-member function isn't necessarily a friend. (And I also find that your edit has changed a lot to the original question.)
  • Steve Jessop
    Steve Jessop almost 15 years
    I can think of uses. For instance, class B might theoretically want to change how it is assigned to A by overloading operator=(A&,B), but B might for some reason not want to define a cast operator to A (for instance because you don't want the other implicit casts to occur). This desire might be unwise, against common practice, etc, but I'm not sure it's nonsensical or that you've (yet) made the case against it.
  • Admin
    Admin almost 15 years
    Well, it doesn't really matter if I haven't made the case against - we have to accept what the standard says. And of course you can do (almost) anything you like via a named friend function.
  • Tim Sylvester
    Tim Sylvester almost 15 years
    It makes sense to disallow such operations on primitive types, but why not allow a global operator[](const MyClass&, int) and make operator[](void,int)* produce an error specifically because of the primitive type?
  • Steve Jessop
    Steve Jessop almost 15 years
    "we have to accept what the standard says" - of course, but that does not exclude seeking a rationale. Usually, the committee made decisions for a reason. You've said the reason this is forbidden is that it "doesn't make sense". As opposed to, say, because some committee member slipped it into the standard while drunk ;-)
  • Admin
    Admin almost 15 years
    @onebyone Sorry, I have no more access than you to the mind-states of standard comittee members. But I believe what I said in the answer is more or less the rationale. It strikes me that your proposal wouldn't work because the thing that becomes the LHS would be a temporary.
  • Steve Jessop
    Steve Jessop almost 15 years
    Why must it be a temporary? What's the difference between defining operator=(A&, const B&) as a free function, and defining swap(A&, B&) as a free function? I don't know, but if anyone does then it probably accounts for the reason that assignment operator has to be a member of A instead of free.