Topic: non-member copy assignment operator for class type (was: namespace


Author: brok@rubikon.pl (Bronek Kozicki)
Date: Tue, 24 Aug 2004 23:49:54 GMT
Raw View
Alberto Barbati wrote:
> I agree that this asymmetry is mildly annoying. However I do not see how
> allowing the definition of operator= at namespace scope helps in
> addressing this issue.

author of class may declare non-member copy assignment operator having form:
   T& operator= (T&, const T&);

Here left operand is an lvalue, thus assignment to an rvalue of class
type T has been suppressed. You brought to my attention that non-member
function can be also defined in class scope:
   struct T
   {
     friend T& operator= (T& lhs, const T& rhs) {return lhs.assign(rhs);}
   };

this would be allowed, too - subject actually should be "non-member copy
assignment operator for class type" (updated).

Non-member copy assignment operator where both operands are T& :
   T& operator= (T&, T&);

should be considered equal to member assignment operator where operand
is T& , thus definition of both (even if latter one has been provided by
compiler) is violation of ODR. Similary, non-member copy assignment
operator where left operand is T& and right operand is const T& :
   T& operator= (T&, const T&);

should be considered equal to member assignment operator where operand
is const T& (thus definion of both is violation of ODR). It would be
possible (but bad style) to define non-member copy assignment operator
taking both operands of type T& and define member copy assignment
operator taking operand of type const T&, and vice-versa. Declaration of
any of these would suppress implicit declaration of copy assignment by
compiler.

Compiler should provide implicit copy assignment operator only if it has
not been declared before its first use. Compiler should also not provide
implicit copy assignment operator if it has been declared in class scope
(ie. friend function) or if class is explicit (vide proposal N1582).

It's to be decided if there should be limitation of left operand type to
non-const reference only (direct motivation behind this proposal), or
allow left operand of const reference (which would be equal to const
member copy assignment operator - bad style, but allowed by current C++
standard), or allow left operand of any type (like other overloaded
operators - but I'm not in favour of this). Template non-member copy
assignment operator should be also allowed, but it would not be equal
with any non-template member form (this equality is discussed above) and
it would not suppress implicit declaration of copy assignment operator
by compiler.


>> There's also asymmetry that modyfing (operator+= etc.) assignment
>> operators can be defined in namespace scope.
>>
>
> Modifying assignment operators are like any other "regular" operator.
> Giving them "assignment" semantic is not something imposed by the
> language but by coding style. You *can* define operator+= to do a
> non-modifying multiplication... if you want to compete in an Obfuscated
> C++ contest ;-)
>
> OTOH, operator=(const&) is different, its semantic is standardized,
> because the compiler may need to provide a default implementation. That
> said, my humble opinion is that the asymmetry is justified and there is
> no need to fix it.

you are right that implicit copy assignment operator may need to be
provided by compiler. Maybe this is the only obstacle that can make C++
Standard Committee reluctant to accept this proposal. However I think I
have proposed clear resolution:


>> compiler
>> would generate assignment operator at the point of first use, if not
>> declared by user till this point; if defined later or in other
>> translation unit, we have violation of ODR,

sincerly I do not think that it's new idea - I must have read some
discussion about it, just do not remember where and when and cannot find
it for reference (and to give credit where credit is due, I apologize
for that). If this has been considered by C++ Standard Committee
already, where I can find relevant proposal?


> Frankly, I do not see a use case where defining a namespace scope
> operator= could be of any help. Could you please provide one?

this proposal, if accepted would allow to:

* disable assignment to rvalue of class type (currently impossible),
thus making behaviour of class more similar to behaviour of builtin
types. This is main motivation behind this proposal, demonstrated in use
case below.

* allow use of common idiom (currently applicable only to builtin types)
   if (calculation() == variable) // ok
   if (calculation() = variable)  // error

* improve separation of interface from implementation, allowing
definition of copy assignment operator without access to private parts
of class, using idiom "copy & swap" or public "assign" member function.

* allow definition of template non-member copy assignment operator -
member template functions are in some respects crippled when compared to
non-member template functions (full specialization of member template
function of nested template class is not possible without specializing
template class first; partial specialization of non-member template
function when allowed by next edition of C++ standard could be used to
overcome this limitation)

Here is use case you asked for:

#include <cstdio>

struct N
{
   template <typename T, typename V>
   void f(T t, V v)
   {
     // perform calculation and put its result
     // into reference returned by functor object t
     V tmp(v);
     // calculation goes here
     tmp *= 2;
     // store result into reference
     t() = tmp;
   }
};

class M
{
   struct K
   {
     int i;
     K(int i) : i (i) {}
   };
   // unable to disable assignment to rvalue of type K

   static K i;

public:
   static K& g1()
   {
     return i;
   }

   static K g2()
   {
     return i; // returning copy!
   }

   static int& g3()
   {
     return i.i;
   }

   static float g4()
   {
     return i.i; // returning copy!
   }

   static void show()
   {
     std::printf("K::i.i == %i\n", i.i);
   }
};

M::K M::i(1);

int main()
{
   N n;

   M::show();

   n.f(M::g1, 30);     // OK
   M::show();          // 60
   n.f(M::g2, 31);     // should be an error, but compiles fine
   M::show();          // 60, which is unexpected output
   n.f(M::g3, 32);     // OK
   M::show();          // 64
   // n.f(M::g4, 34.43);     // error, as it should be
}



B.

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]