Why cannot a non-member function be used for overloading the assignment operator?
Asked Answered
H

9

34

The assignment operator can be overloaded using a member function but not a non-member friend function:

class Test
{
    int a;
public:
    Test(int x)
        :a(x)
    {}
    friend Test& operator=(Test &obj1, Test &obj2);
};

Test& operator=(Test &obj1, Test &obj2)//Not implemented fully. just for test.
{
    return obj1;
}

It causes this error:

error C2801: 'operator =' must be a non-static member

Why cannot a friend function be used for overloading the assignment operator? The compiler is allowing to overload other operators such as += and -= using friend. What's the inherent problem/limitation in supporting operator=?

Hartsell answered 14/10, 2010 at 13:29 Comment(0)
U
32

Because the default operator= provided by the compiler (the memberwise copy one) would always take precedence. I.e. your friend operator= would never be called.

EDIT: This answer is answering the

Whats the inherent problem/limitation in supporting = operator ?

portion of the question. The other answers here quote the portion of the standard that says you can't do it, but this is most likely why that portion of the standard was written that way.

Ungrudging answered 14/10, 2010 at 13:36 Comment(9)
+1: I am jealous of the clarity you have provided behind the actual reasoning.Liveryman
Sorry, but that just plain incorrect and doesn't make any sense. Why would the compiler's operator take precedence? For those operators that can be declared as standalone functions, declaring both member version and standalone version leads to ambiguity, not to member function "taking precedence". What is the logic behind the statement in this answer then?Threw
@AndreyT and @Billy Oneal: Both of you are right, in different contexts. If the assignment was performed inside a class method, because of the lookup rules, the member function (in this case generated by the compiler) would take precedence and hide the namespace scope operator=. If the assignment happens outside of the class scope, then there would be ambiguity and the compiler would fail. While this cannot be tested with operator= it is fairly simple to generate a test with operator+= (or any other operator that can be implemented both as member and as free function)Sills
@DavidRodríguez-dribeas "If the assignment was performed inside a class method, because of the lookup rules, the member function (in this case generated by the compiler) would take precedence" No, it would not.Boneblack
@curiousguy: I don't see why you'd downvote my answer because of a comment made by David. (Though, as far as I am aware, David is correct)Ungrudging
@BillyONeal "you'd downvote my answer because of a comment made by David" I obviously didn't do that.Boneblack
@curiousguy: I think you misunderstood my comment, it is just an argument on what would happen if the assignment operator could be implemented as a free function (considering that the compiler implicitly declares one if the user doesn't) with respect to lookup rules, and as far as I know the argument holds and can be tested. Your test calls += outside of the context of a member where there is ambiguity --i.e. that confirms the second part of my statement. Now try doing it inside a member and see the first part of the argument.Sills
@curiousguy: You are right here. When I wrote it I tested by adding a member function that was defined inside the class definition, and did not realize that at that point the free function had not yet been defined.Sills
@DavidRodríguez-dribeas BTW, the ambiguity could be avoided in some cases with a operator=(A&, A&) prototype vs. A::operator=(const A&). It is still a silly idea. With a suppressed operator=, it would make more sense.Boneblack
T
39

Firstly, it should be noted that this has nothing to do with the operator being implemented as a friend specifically. It is really about implementing the copy-assignment as a member function or as a non-member (standalone) function. Whether that standalone function is going to be a friend or not is completely irrelevant: it might be, it might not be, depending on what it wants to access inside the class.

Now, the answer to this question is given in D&E book (The Design and Evolution of C++). The reason for this is that the compiler always declares/defines a member copy-assignment operator for the class (if you don't declare your own member copy-assignment operator).

If the language also allowed declaring copy-assignment operator as a standalone (non-member) function, you could end up with the following

// Class definition
class SomeClass {
  // No copy-assignment operator declared here
  // so the compiler declares its own implicitly
  ...
};

SomeClass a, b;

void foo() {
  a = b;
  // The code here will use the compiler-declared copy-assignment for `SomeClass`
  // because it doesn't know anything about any other copy-assignment operators
}

// Your standalone assignment operator
SomeClass& operator =(SomeClass& lhs, const SomeClass& rhs);

void bar() {
  a = b;
  // The code here will use your standalone copy-assigment for `SomeClass`
  // and not the compiler-declared one 
}

As seen in the above example, the semantics of the copy-assignment would change in the middle of the translation unit - before the declaration of your standalone operator the compiler's version is used. After the declaration your version is used. The behavior of the program will change depending on where you put the declaration of your standalone copy-assignment operator.

This was considered unacceptably dangerous (and it is), so C++ doesn't allow copy-assignment operator to be declared as a standalone function.

It is true that in your particular example, which happens to use a friend function specifically, the operator is declared very early, inside the class definition (since that's how friends are declared). So, in your case the compiler will, of course, know about the existence of your operator right away. However, from the point of view of C++ language the general issue is not related to friend functions in any way. From the point of view of C++ language it is about member functions vs. non-member functions, and non-member overloading of copy-assignment is just prohibited entirely for the reasons described above.

Threw answered 14/10, 2010 at 13:51 Comment(10)
Can't that be caught by making such usage ambiguous?Liveryman
@Chubsdad: Yes, but what would be the point then? It would always be ambiguous. You wouldn't be able to use your standalone operator at all.Threw
@AndreyT: hmm.. that's true. I agreeLiveryman
@Liveryman : The Design and Evolution of C++Spirelet
@Prasoon Saurav: Oh yes. I should have guessed that! Thanks.Liveryman
"This was considered unacceptably dangerous (and it is)," why?Boneblack
@curiousguy: It is dangerous for what I perceive as a rather obvious reason: a seemingly innocent relocation of a completely independent piece of code (definition of user-defined operator =) could completely change the semantics of the surrounding code. If some piece of code was located before the definition and now happened to be located after the definition, its semantics would change, since it would automatically switch from compiler-provided operator = to user-defined operator =.Threw
@AndreyT This is potentially the case with all cases of overloading with convertible types.Boneblack
@curiousguy: Certainly. But apparently it was decided that the assignment operator is so important that it has to be given special treatment. Again, this is the rationale presented in the D&E book.Threw
Couldn't you delete the implicitly declared assignment operator?Cockade
U
32

Because the default operator= provided by the compiler (the memberwise copy one) would always take precedence. I.e. your friend operator= would never be called.

EDIT: This answer is answering the

Whats the inherent problem/limitation in supporting = operator ?

portion of the question. The other answers here quote the portion of the standard that says you can't do it, but this is most likely why that portion of the standard was written that way.

Ungrudging answered 14/10, 2010 at 13:36 Comment(9)
+1: I am jealous of the clarity you have provided behind the actual reasoning.Liveryman
Sorry, but that just plain incorrect and doesn't make any sense. Why would the compiler's operator take precedence? For those operators that can be declared as standalone functions, declaring both member version and standalone version leads to ambiguity, not to member function "taking precedence". What is the logic behind the statement in this answer then?Threw
@AndreyT and @Billy Oneal: Both of you are right, in different contexts. If the assignment was performed inside a class method, because of the lookup rules, the member function (in this case generated by the compiler) would take precedence and hide the namespace scope operator=. If the assignment happens outside of the class scope, then there would be ambiguity and the compiler would fail. While this cannot be tested with operator= it is fairly simple to generate a test with operator+= (or any other operator that can be implemented both as member and as free function)Sills
@DavidRodríguez-dribeas "If the assignment was performed inside a class method, because of the lookup rules, the member function (in this case generated by the compiler) would take precedence" No, it would not.Boneblack
@curiousguy: I don't see why you'd downvote my answer because of a comment made by David. (Though, as far as I am aware, David is correct)Ungrudging
@BillyONeal "you'd downvote my answer because of a comment made by David" I obviously didn't do that.Boneblack
@curiousguy: I think you misunderstood my comment, it is just an argument on what would happen if the assignment operator could be implemented as a free function (considering that the compiler implicitly declares one if the user doesn't) with respect to lookup rules, and as far as I know the argument holds and can be tested. Your test calls += outside of the context of a member where there is ambiguity --i.e. that confirms the second part of my statement. Now try doing it inside a member and see the first part of the argument.Sills
@curiousguy: You are right here. When I wrote it I tested by adding a member function that was defined inside the class definition, and did not realize that at that point the free function had not yet been defined.Sills
@DavidRodríguez-dribeas BTW, the ambiguity could be avoided in some cases with a operator=(A&, A&) prototype vs. A::operator=(const A&). It is still a silly idea. With a suppressed operator=, it would make more sense.Boneblack
I
8

Because there are some operators which MUST be members. These operators are:
operator[]
operator=
operator()
operator->

and type conversion operators, like operator int.

Although one might be able to explain why exactly operator = must be a member, their argument cannot apply to others in the list, which makes me believe that the answer to "Why" is "Just because".

HTH

Interferometer answered 14/10, 2010 at 13:38 Comment(4)
Well, I believe that is the OP's question: why must they be members?Threw
And operator. cannot be overloaded at all, which can be a shame sometimes as you cannot implement a smart_reference.Prosit
@CashCow: Stroustrup gave a profound rationale as to why it is not allowed to overload . in "The Design and Evolution of C++". There are more which cannot be overloaded, like :: .* ?: sizeof etc.Interferometer
@Prosit "And operator. cannot be overloaded at all," operator.() would have to return a reference type. Reference to what?Boneblack
L
8

$13.5.3 - "An assignment operator shall be implemented by a non-static member function with exactly one parameter. Because a copy assignment operator operator= is implicitly declared for a class if not declared by the user (12.8), a base class assignment operator is always hidden by the copy assignment operator of the derived class."

Liveryman answered 14/10, 2010 at 13:40 Comment(4)
That doesn't explain why e.g. operator [] can't be overloaded as a freestanding function, so I guess "It's just the way it is" is the most accurate answerInterferometer
+1 -- the point of my answer but better standard quoting backing it.Ungrudging
@Armen Tsirunyan: Yes. I tried to give reference to the actual quote in the Standard that talk about this aspect. Billy's and other answers give a good reason behind the same.Liveryman
This answer, especially the emphasized part, talks about name hiding between the derived and base classes. What does it have to do with the question?Threw
T
3

operator= is a special member function that the compiler will provide if you don't declare it yourself. Because of this special status of operator= it makes sense ro require it to be a member function, so there is no possibility of there being both a compiler-generated member operator= and a user-declared friend operator= and no possibility of choosing between the two.

Torino answered 14/10, 2010 at 13:42 Comment(0)
A
1

Why friend function can't be used for overloading assignment operator?

Short answer: Just because.

Somewhat longer answer: That's the way the syntax was fixed. A few operators have to be member functions. The assignment operator is one of the,

Arraignment answered 14/10, 2010 at 13:40 Comment(0)
W
0

The intention of operator= is an assignment operation to the current object. Then the LHS, or lvalue, is an object of the same type.

Consider a case where the LHS is an integer or some other type. That is a case handled by operator int() or a corresponding operator T() function. Hence the type of the LHS is already defined, but a non-member operator= function could violate this.

Hence it is avoided.

Waterborne answered 4/7, 2012 at 13:16 Comment(0)
A
0

This post applies to C++11

Why would someone want a non-member operator=? Well, with a member operator= then the following code is possible:

Test const &ref = ( Test() = something ); 

which creates a dangling reference. A non-member operator would fix this:

Test& operator=(Test &obj1, Test obj2)

because now the prvalue Test() will fail to bind to obj1. In fact this signature would enforce that we never return a dangling reference (unless we were supplied with one, of course) - the function always returns a "valid" lvalue because it enforces being called with an lvalue.

However in C++11 there is now a way to specify that a member function can only be called on lvalues, so you could achieve the same goal by writing the member function:

Test &operator=(Test obj2) &
//                        ^^^

Now the above code with dangling reference will fail to compile.


NB. operator= should take the right-hand-side by either value or const reference. Taking by value is useful when implementing the copy and swap idiom, a technique for easily writing safe (but not necessarily the fastest) copy-assignment and move-assignment operators.

Alltime answered 19/2, 2015 at 3:59 Comment(4)
This was posted a while ago, but I happened along this while trying to solve this, so thought I'd mention: suggesting that operator= take by value and then implementing via swap is not good advice. Default swap is implemented via move construction & assignment, but operator= by value implements copy & move assignment simultaneously. This means you'll need to implement swap yourself (to avoid recursion), so you're still implementing the same number of methods, but your move assignment is much slower than necessary.Relay
Better suggestion: implement move assignment yourself. Then you get an efficient swap in most cases for free (in either scenario you of course have to implement copy/move construction). Then, implement copy assignment using CAS if you prefer strong exception safety to performance, otherwise implement it from scratch. Either way operator= should never take by value.Relay
@NirFriedman updated. It depends on the details of the class really. In the by-value approach you only need to swap in one place so you could put the swap logic inside the operator= body. CAS makes your code very simple and exception-safe but as you say, often not the most efficient.Alltime
By value assignment (unified assignment) is not really good for exception safety either, because your move assignment should typically be no except. But here, because it's the same function as your copy assignment (which is not typically nothrow, though with CAS it is strong guarantee), you can't easily mark it such.Relay
C
0

Because there is already an implicit operator overloading function for '=' in the class to do shallow copying. So even if you overload using a a friend function you will never be able to call it as any call made by us would call the implicit shallow copying method rather than the overloaded friend function.

Castera answered 28/5, 2015 at 4:35 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.