Topic: Synthesized assignment and virtual bases


Author: jason@cygnus.com (Jason Merrill)
Date: Wed, 9 Feb 1994 00:08:28 GMT
Raw View
12.8 says, "Objects representing virtual base classes will be assigned only
once by a generated assignment operator."  This is trickier to support than
is the equivalent statement for constructors, since a base initializer for
a virtual base that is called by a class other than the most fully-derived
for that object is ignored.

Consider the following code.  Should D have a synthesized assignment
operator?  How many times should A::op= be called?  I would say yes, and
one, by the above rule.  xlC 1.2 and g++ 2.5.8 call it once, Cfront 3.0.1
calls it twice.

*************
int count = 0;
extern "C" int printf (const char *, ...);

class A {
 public:
  A& operator = (const A&) { count++; return *this; }
};

class B: virtual public A { };
class C: virtual public A { };
class D: public B, public C { };

int main()
{
  D a, b;
  a = b;
  printf ("%d\n", count);
}
*************

Now suppose B defines op= to do nothing.  Now how many times should A::op=
be called?  I would say zero, because the compiler does not have control
over all paths to A, and so it should abdicate responsibility for
initializing the A part of D.  xlC doesn't call it, g++ and Cfront call it
once.

Suppose, instead, that B and C both inherit from A via virtual private
inheritance?  Should D still have a synthesized assignment operator?  For
the compiler to make sure that A::op= is only called once, it needs to
prowl the inheritance DAG, but D can't refer to A::op=, so maybe it
shouldn't synthesize op=.  xlC doesn't, even if both B and C define op=.
Cfront and g++ do.

Thoughts?

Jason




Author: daniels@biles.com (Brad Daniels)
Date: Wed, 9 Feb 1994 16:41:37 GMT
Raw View
In article <JASON.94Feb8160828@deneb.cygnus.com>,
Jason Merrill <jason@cygnus.com> wrote:
>12.8 says, "Objects representing virtual base classes will be assigned only
>once by a generated assignment operator."  This is trickier to support than
>is the equivalent statement for constructors, since a base initializer for
>a virtual base that is called by a class other than the most fully-derived
>for that object is ignored.
>
>Consider the following code.  Should D have a synthesized assignment
>operator?  How many times should A::op= be called?  I would say yes, and
>one, by the above rule.  xlC 1.2 and g++ 2.5.8 call it once, Cfront 3.0.1
>calls it twice.

Yes/one sounds right to me, too...  The problem is the same as that of
synthesizing a copy ctor.

>*************
>int count = 0;
>extern "C" int printf (const char *, ...);
>
>class A {
> public:
>  A& operator = (const A&) { count++; return *this; }
>};
>
>class B: virtual public A { };
>class C: virtual public A { };
>class D: public B, public C { };
>
>int main()
>{
>  D a, b;
>  a = b;
>  printf ("%d\n", count);
>}
>*************
>
>Now suppose B defines op= to do nothing.  Now how many times should A::op=
>be called?  I would say zero, because the compiler does not have control
>over all paths to A, and so it should abdicate responsibility for
>initializing the A part of D.  xlC doesn't call it, g++ and Cfront call it
>once.

I think it should either be called once, or the case should be flagged as
ambiguous.  If B's op= doesn't invoke A's op=, while C's (being a default
op=) does, it's impossible for the compiler to determine whether it's more
important not to change A (to satisfy B's expectations) or to assign A
(to satisfy C's expectations).  I personally favor throwing the case out
as ambiguous, since you can have similar problems even with both B and C
having defined op=;

class A {
 public:
  A& operator = (const A&) { count++; return *this; }
  A& operator = (int i) { count+=i; return *this; }
};

class B: virtual public A {
 public:
  B& operator = (const B &ob) { A::operator=(ob); return *this; }
};
class C: virtual public A {
  int i;
 public:
  C& operator = (const C &ob) { A::operator=(ob.i); return *this; }
};
class D: public B, public C { };

int main()
{
  D a, b;
  a = b;
  printf ("%d\n", count);
}

In the above (obviously incomplete) example, any result might surprise
future invocations of either B or C.  I think flagging an error is usually
the best way to handle such cases.

>Suppose, instead, that B and C both inherit from A via virtual private
>inheritance?  Should D still have a synthesized assignment operator?  For
>the compiler to make sure that A::op= is only called once, it needs to
>prowl the inheritance DAG, but D can't refer to A::op=, so maybe it
>shouldn't synthesize op=.  xlC doesn't, even if both B and C define op=.
>Cfront and g++ do.