Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

user-defined op= for type with reference member

1 view
Skip to first unread message

Paul Bibbings

unread,
Jun 11, 2010, 5:21:09 PM6/11/10
to
Since I have used the following in another post, can someone just
confirm (or otherwise) whether the following definition of a
user-defined op= for a type with a reference member is well defined?

class AType
{
public:
AType(int& i)
: i_(i)
{ }
// ...
AType& operator=(const AType& other)
{
if (this != &other)
{
this->~Atype();
new (this) AType(other);
}
return *this;
}
private:
int& i_;
};

According to my reading of the example given in [basic.life] §3.8/7 I
believe that it is, in this instance (since the constructor doesn't
throw, except on bad_alloc).

Regards

Paul Bibbings

Leigh Johnston

unread,
Jun 11, 2010, 5:27:51 PM6/11/10
to

"Paul Bibbings" <paul.b...@gmail.com> wrote in message
news:87iq5pe...@gmail.com...

Attempting to use this trick to reseat a reference or destruct/re-construct
const members is UB IIRC, there was a thread about this a few months ago I
think.

/Leigh

Pete Becker

unread,
Jun 11, 2010, 5:29:53 PM6/11/10
to

Yes, it's well-defined, but it's a really bad idea:

class BType : public AType
{
public:
BType& operator=(const BType& other)
{
return AType::operator==(other);
}
};

BType b1, b2;
b2 = b1; // nasty

In this particular example, there's almost certainly no nasty behavior.
But add a virtual function to AType (and make AType's destructor
virtual) and override the function in BType.

AType *at = &b2;
at->virtual_function();

Now things are messy, because the code says that b2 has type BType, but
BType's constructor has not been called.

--
Pete
Roundhouse Consulting, Ltd. (www.versatilecoding.com) Author of "The
Standard C++ Library Extensions: a Tutorial and Reference
(www.petebecker.com/tr1book)

Leigh Johnston

unread,
Jun 11, 2010, 5:34:15 PM6/11/10
to

"Pete Becker" <pe...@versatilecoding.com> wrote in message
news:2010061111295323604-pete@versatilecodingcom...

It is well defined in the sense that it is defined to be undefined
behaviour. You cannot reseat references.

/Leigh

Paul Bibbings

unread,
Jun 11, 2010, 5:38:41 PM6/11/10
to
"Leigh Johnston" <le...@i42.co.uk> writes:

I had that same recollection, but take a look at §3.8/7, where the
following example is given:

struct C {
int i;
void f();
const C& operator=( const C& );
};

const C& C::operator=( const C& other)
{
if ( this != &other ) {
this->~˜C(); //lifetime of *this ends
new (this) C(other); // new object of type C created
f(); //well-defined
}
return *this;
}

C c1;
C c2;
c1 = c2; // well-defined
c1.f(); //well-defined; c1 refers to a new object of type C

The only relevant difference here is the reference data member, but I
have not been able to find a reason why this should present an especial
problem. The copy constructor can seat a reference, so I can't see why
using it in the context of a placement new (if that is the right term)
should be problematic. I will try and find the earlier thread.

Regards

Paul Bibbings

Paul Bibbings

unread,
Jun 11, 2010, 5:41:51 PM6/11/10
to
"Leigh Johnston" <le...@i42.co.uk> writes:

But you are not reseating a reference, surely. The above code -
notwithstanding Pete's comments which I am still digesting - destructs a
constructed object and creates a new one in its place. That is a very
different beast, wouldn't you say?

Regards

Paul Bibbings

Leigh Johnston

unread,
Jun 11, 2010, 5:42:58 PM6/11/10
to

"Paul Bibbings" <paul.b...@gmail.com> wrote in message

news:87aar1d...@gmail.com...

From 3.8/7:

"If, after the lifetime of an object has ended and before the storage which
the object occupied is reused or
released, a new object is created at the storage location which the original
object occupied, a pointer that
pointed to the original object, a reference that referred to the original
object, or the name of the original
object will automatically refer to the new object and, once the lifetime of
the new object has started, can
be used to manipulate the new object, if:
— the type of the original object is not const-qualified, and, if a class
type, does not contain any non-static
data member whose type is const-qualified >>>>>> or a reference type <<<<<<,
and"

/Leigh

Leigh Johnston

unread,
Jun 11, 2010, 5:43:41 PM6/11/10
to

"Paul Bibbings" <paul.b...@gmail.com> wrote in message

news:87631pd...@gmail.com...

It is UB, see my reply else-thread.

/Leigh

Kai-Uwe Bux

unread,
Jun 11, 2010, 5:57:36 PM6/11/10
to
Paul Bibbings wrote:

It is undefined behavior according to [3.8/7, item 3], which requires:

...


the type of the original object is not const-qualified, and, if a class
type, does not contain any non-static data member whose type is const-
qualified or a reference type,

...

Since AType contains a non-static reference member, the trick does not work
for AType. Note that in the example in the standard, there is no non-static
reference member.


I know: it's a bummer since classes with reference members are about the
only cases, where one might even consider this trickery (which is poor form
anyway).


Best

Kai-Uwe Bux

Francesco S. Carta

unread,
Jun 11, 2010, 6:17:32 PM6/11/10
to
"Leigh Johnston" <le...@i42.co.uk> wrote:
> "Pete Becker" <p...@versatilecoding.com> wrote in message


I'm not so sure whether the following could come interesting in this
case (nothing special for the most experienced ones, I suppose): it is
possible to store the reference as a pointer and create a "resettable
reference" in either of the following ways:

-------
#include <iostream>

using namespace std;

class IntByRef {
public:
explicit IntByRef(int& i) : ptr(&i) {};
operator int&() {
return *ptr;
}
int& operator=(int& i) {
ptr= &i;
return *ptr;
}
private:
int* ptr;
};

int main()
{

int local = 8;

IntByRef ibr(local);

cout << ibr << endl; // prints 8

local = 10;

cout << ibr << endl; // prints 10

int local2 = 42;

ibr = local2;

cout << ibr << endl; // prints 42

int& intref = ibr;

++ibr;
ibr *= 2;
ibr -= 8;

cout << intref << endl; // prints 78;

return 0;
}
-------


-------
#include <iostream>

using namespace std;

class SimplerIntByRef {
public:
SimplerIntByRef (int& i) : ptr(&i) {};
operator int&() {
return *ptr;
}
private:
int* ptr;
};

int main()
{

int local = 8;

SimplerIntByRef ibr(local);

cout << ibr << endl; // prints 8

local = 10;

cout << ibr << endl; // prints 10

int local2 = 42;

ibr = local2;

cout << ibr << endl; // prints 42

int& intref = ibr;

++ibr;
ibr *= 2;
ibr -= 8;

cout << intref << endl; // prints 78;

return 0;
}
-------

main() is practically identical for both cases, you can apply all
combined operator+equals to an IntByRef as well as all pre and post
increment/decrement, the only thing that you can't assign to it is a
constant or a temporary (that would be a plain rvalue) - by the means
of using operator= or by using the implicit ctor in the simpler case.
You can only (re)assign it to "point" to another (non-const) variable.

Modulo mistakes and misunderstandings as usual, I'm ready to have my
"view" fixed :-)

--
FSC
http://userscripts.org/scripts/show/59948

Paul Bibbings

unread,
Jun 11, 2010, 7:09:56 PM6/11/10
to
Kai-Uwe Bux <jkher...@gmx.net> writes:

It is indeed a bummer. I had somehow managed to `parse' 3.8/7
incorrectly and thought that I could escape it which, of course, I
can't. But that leaves me thinking, how /do/ you write a
copy-assignment operator for such a class with meaningful semantics?

Regards

Paul Bibbings

Pete Becker

unread,
Jun 11, 2010, 7:15:03 PM6/11/10
to
On 2010-06-11 13:09:56 -1000, Paul Bibbings said:

>
> It is indeed a bummer. I had somehow managed to `parse' 3.8/7
> incorrectly and thought that I could escape it which, of course, I
> can't. But that leaves me thinking, how /do/ you write a
> copy-assignment operator for such a class with meaningful semantics?
>

It depends on how you define "meaningful semantics". If that includes
leaving the reference member alone, then the obvious assignment
operator works just fine. If you need to change what the reference
member refers to, then either make it a pointer or use TR1's
reference_wrapper template.

Kai-Uwe Bux

unread,
Jun 11, 2010, 7:26:53 PM6/11/10
to
Paul Bibbings wrote:

[...]


> But that leaves me thinking, how /do/ you write a
> copy-assignment operator for such a class with meaningful semantics?

I'd say, you don't. If the class shall have an assignment operator, then I
would make sure that it has only pointer members (or better, a little smart
pointer like thingy that is closer to a reference in that it cannot be a
null pointer).


Best

Kai-Uwe Bux

Marcel Müller

unread,
Jun 13, 2010, 4:44:12 AM6/13/10
to
Leigh Johnston wrote:
> From 3.8/7:
>
> "If, after the lifetime of an object has ended and before the storage
> which the object occupied is reused or
> released, a new object is created at the storage location which the
> original object occupied, a pointer that
> pointed to the original object, a reference that referred to the
> original object, or the name of the original
> object will automatically refer to the new object and, once the lifetime
> of the new object has started, can
> be used to manipulate the new object, if:
> — the type of the original object is not const-qualified, and, if a
> class type, does not contain any non-static
> data member whose type is const-qualified >>>>>> or a reference type
> <<<<<<, and"

Well, the question is who holds a pointer (or reference) to this at the
time of the reconstruction. If nobody does it, I cannot see any UB.
However, I can not imagine a use case of an assignment, without an
active reference to an assigned class.

But other design patterns with in place reconstruction may still be
defined behavior. Think about a factory that reuses memory of class
instances that are orphaned for some reason, and this is not detected
before the factory call. E.g. a connection pool, where the connection
parameters must match for the instances to be reused. Of course, no
assignment operator so far.
But one could implement this by an assignment operator as long as any
further access to the newly constructed instance is done through the
return value of the assignment operator. The compiler cannot assume that
this return value is *this and so it is just a new class instance that
happens to use the same storage. Of course, I would call this /very bad/
design, because the assignment operator behaves unexpected in the way
that it destroys *this. Any reuse of storage should be done /outside/
the class instance that occupies the storage. This reminds me of
discussions about 'delete this;'.


Marcel

Leigh Johnston

unread,
Jun 13, 2010, 8:30:09 AM6/13/10
to

"Marcel Müller" <news.5...@spamgourmet.com> wrote in message
news:4c149a5c$0$6981$9b4e...@newsspool4.arcor-online.net...

It is UB to reseat a reference. You are attempting to reseat a reference if
you call destructor and placement new in a class's assignment operator i.e.

"after the lifetime of an object has ended and before the storage which the

object occupied is reused or released". Read the above section from the
standard again. Violating a requirement in the standard is UB and arguing
this point is pointless.

/Leigh

Marcel Müller

unread,
Jun 13, 2010, 9:40:55 AM6/13/10
to
Leigh Johnston wrote:
> It is UB to reseat a reference. You are attempting to reseat a
> reference if you call destructor and placement new in a class's
> assignment operator i.e. "after the lifetime of an object has ended and
> before the storage which the object occupied is reused or released".
> Read the above section from the standard again. Violating a requirement
> in the standard is UB and arguing this point is pointless.

The point is that I do not talk about the /same/ object. And if we do
not talk about the same object, this restriction of the standard does
not apply. It is only a new object which happens to use the same
storage. There is nothing wrong so far as long as no one relies on that.


Marcel

Paul Bibbings

unread,
Jun 13, 2010, 10:58:42 AM6/13/10
to
On Jun 13, 1:30 pm, "Leigh Johnston" <le...@i42.co.uk> wrote:

> It is UB to reseat a reference.  You are attempting to reseat a reference if
> you call destructor and placement new in a class's assignment operator i.e.
> "after the lifetime of an object has ended and before the storage which the
> object occupied is reused or  released". Read the above section from the
> standard again.  Violating a requirement in the standard is UB and arguing
> this point is pointless.
>
> /Leigh

I don't disagree, on the whole, with the conclusions reached in this
thread, but I am having some problem with it being stated in terms of
it
being "UB to reseat a reference." To my mind, "if you call destructor
and placement new in a class's assignment operator" you are *not*
thereby "reseat[ing] a reference." You *are* destructing an object
and
creating a new one in its place, with the attendant UB that has been
identified in the examples given; however, there is, to my mind, no
reference-reseating simply because, with the destruction of the
original
object, it's reference-member is destroyed with it and a *new* one
created in its place.

Now, if you consider the following:

#include <new>

class HasRefMem {
public:
HasRefMem(int& i)
: i_(i)
{ }
HasRefMem& operator=(const HasRefMem& other)
{
if (this != &other)
{
this->~HasRefMem();
new (this) HasRefMem(other);
}
return *this;
}
operator int&() { return i_; }
private:
int& i_;
};

int main()
{
int i, j;
HasRefMem hrmi(i);
HasRefMem hrmj(j);
int& i_ref = hrmi; // #1
hrmi = hrmj; // #2
}

then I might be able to consider i_ref (in line #1) as having been
`reseated' in line #2.

Regards

Paul Bibbings

Leigh Johnston

unread,
Jun 13, 2010, 11:04:41 AM6/13/10
to

"Marcel Müller" <news.5...@spamgourmet.com> wrote in message

news:4c14dfe7$0$7665$9b4e...@newsspool1.arcor-online.net...

It is the same object as far the rest of the program is concerned. You are
invoking UB. End of. Stop being a fucktard and read section 3.8 of the
standard.

/Leigh

Paul Bibbings

unread,
Jun 13, 2010, 11:04:42 AM6/13/10
to

Well, actually ... no (replying to myself). i_ref will continue to
refer to i.

Regards

Paul Bibbings

Keith H Duggar

unread,
Jun 13, 2010, 11:43:57 AM6/13/10
to
On Jun 13, 9:40 am, Marcel Müller <news.5.ma...@spamgourmet.com>
wrote:

Please read the section of the standard again more carefully.
It does not refer to "the same object". It specifically refers
to the following noun phrases:

"an object"
"the storage which the [an] object occupied"
"a new object"
"the storage location which the original [an] object occupied"

ie it discusses the /storage/ that the /original/ and /new/
objects occupy. And it explicitly distinguishes between two
different objects: /original/ and /new/. In other words,
there is no "/same/ object" requirement as you thought.
Rather there is a same /storage/ requirement.

3.8/7 :
"If, after the lifetime of an object has ended and before the
storage which the object occupied is reused or released, a new


object is created at the storage location which the original
object occupied, a pointer that pointed to the original object,
a reference that referred to the original object, or the name of
the original object will automatically refer to the new object
and, once the lifetime of the new object has started, can be

used to manipulate the new object, if: — the type of the original


object is not const-qualified, and, if a class type, does not
contain any non-static data member whose type is const-qualified

or a reference type, ..."

KHD

Bo Persson

unread,
Jun 13, 2010, 2:01:40 PM6/13/10
to
Keith H Duggar wrote:
> On Jun 13, 9:40 am, Marcel M?ºller <news.5.ma...@spamgourmet.com>

How is it not the same object after an assignment? That doesn't create
a new object, does it?


Bo Persson


Keith H Duggar

unread,
Jun 13, 2010, 2:25:15 PM6/13/10
to
On Jun 13, 2:01 pm, "Bo Persson" <b...@gmb.dk> wrote:
> Keith H Duggar wrote:
> > On Jun 13, 9:40 am, Marcel M?ºller <news.5.ma...@spamgourmet.com>

It does when a destructor + constructor sequence is called in
the assignment (the case being discussed). Below is a relevant
portion from the standard regarding object lifetime:

3.8 Object Lifetime
[basic.life]

1 The lifetime of an object is a runtime property of the object.
The
lifetime of an object of type T begins when:

--storage with the proper alignment and size for type T is
obtained,
and

--if T is a class type with a non-trivial constructor
(_class.ctor_),
the constructor call has completed.

The lifetime of an object of type T ends when:

--if T is a class type with a non-trivial destructor
(_class.dtor_),
_________________________
9) On some implementations, it causes a system-generated
runtime
fault.

the destructor call starts, or

--the storage which the object occupies is reused or released.

KHD

Bo Persson

unread,
Jun 14, 2010, 6:50:37 AM6/14/10
to
Keith H Duggar wrote:
> On Jun 13, 2:01 pm, "Bo Persson" <b...@gmb.dk> wrote:
>> Keith H Duggar wrote:
>>> On Jun 13, 9:40 am, Marcel M?¬?ller <news.5.ma...@spamgourmet.com>

My point was rather the same as Leigh(?) above, that an assignment
doesn't create a new object so the reuse rule doesn't apply.

In the code

SomeObject x, y;

x = y;

it is the same object x both before and after the assignment. This is
not a reuse of storage.


Bo Persson


Paul Bibbings

unread,
Jun 14, 2010, 8:57:28 AM6/14/10
to
"Bo Persson" <b...@gmb.dk> writes:

Remember that, in the example code originally given, the only issue that
resulted in undefined behaviour was the fact that the class for which
the op= was being defined contained a reference member. If we put that
aside for a moment we get, effectively, the example given in
[basic.life] §3.8/7, which I give below, complete with (unchanged)
comments from the text:

struct C {
int i;
void f();
const C& operator=( const C& );
};

const C& C::operator=( const C& other)
{
if ( this != &other ) {

this->˜C(); // lifetime of *this ends


new (this) C(other); // new object of type C created
f(); // well-defined
}
return *this;
}

C c1;
C c2;
c1 = c2; // well-defined

c1.f(); // well-defined; c1 refers to a new object of type C

You will see clearly the succession "lifetime of *this ends," "new
object of type C created" and, in the last line "c1 refers to a *new*
object of type C."

All this *because* of the particular way in which the assignment has
been implemented.

Regards

Paul Bibbings

Paul Bibbings

unread,
Jun 14, 2010, 4:55:21 PM6/14/10
to

Personally, I cannot see what the point would be of class modelling a reference,
albeit a reseatable one, that didn't permit the simple assignment:

int i = 1;
IntByRef ibr(i);
ibr = 2;

For myself, I would have the assignment operators assign *values*, and have the
reseating required to be done explicitly. Something like:

template<typename T>
class ReseatableRef
{
ReseatableRef(T& t): t_ptr(&t) { }
ReseatableRef& operator=(const T& t) // value assignment
{
*t_ptr = t;
return *this;
}
ReseatableRef& operator=(const ReseatableRef& other) // ditto!!!
{
*t_ptr = *other.t_ptr;
return *this;
}
ReseatableRef& reseat(T& t)
{
t_ptr = &t;
return *this;
}
operator T&() { return *t_ptr; }
operator T() const { return *t_ptr; }
T * operator&() const { return t_ptr; }
private:
T *t_ptr;
};

int main()
{
int local = 8;

iref_t ibr(local);
std::cout << "1: ibr = " << ibr << '\n';

local = 10;
std::cout << "2: ibr = " << ibr << '\n';

ibr = 42; // !!!
std::cout << "3: ibr = " << ibr << '\n';

local = 0;
int local2 = 1;
ibr.reseat(local2);
std::cout << "4: ibr = " << ibr << '\n';

int& local22 = local2;
int& local222 = local22;

std::cout << "&local2 = " << &local2 << '\n';
std::cout << "&local22 = " << &local22 << '\n';
std::cout << "&local222 = " << &local222 << '\n';

iref_t ibr2(ibr);
iref_t ibr3(ibr2);

std::cout << "&ibr = " << &ibr << '\n';
std::cout << "&ibr2 = " << &ibr2 << '\n';
std::cout << "&ibr3 = " << &ibr3 << '\n';
}

/**
* Output:
* 1: ibr = 8
* 2: ibr = 10
* 3: ibr = 42
* 4: ibr = 1
* &local2 = 0x22cd08
* &local22 = 0x22cd08
* &local222 = 0x22cd08
* &ibr = 0x22cd08
* &ibr2 = 0x22cd08
* &ibr3 = 0x22cd08
*/

I think that this would be closer to the behaviour of an ordinary reference over
all, with the *addition* that it can be reseated. Of course, it also allows for
the creation of a `reference to a reference':-)

Regards

Paul Bibbings

James Kanze

unread,
Jun 21, 2010, 6:46:39 PM6/21/10
to
On Jun 11, 10:27 pm, "Leigh Johnston" <le...@i42.co.uk> wrote:
> "Paul Bibbings" <paul.bibbi...@gmail.com> wrote in message

> news:87iq5pe...@gmail.com...
> > Since I have used the following in another post, can someone
> > just confirm (or otherwise) whether the following definition
> > of a user-defined op= for a type with a reference member is
> > well defined?

> > class AType
> > {
> > public:
> > AType(int& i)
> > : i_(i)
> > { }
> > // ...
> > AType& operator=(const AType& other)
> > {
> > if (this != &other)
> > {
> > this->~Atype();
> > new (this) AType(other);
> > }
> > return *this;
> > }
> > private:
> > int& i_;
> > };

> > According to my reading of the example given in [basic.life]
> > §3.8/7 I believe that it is, in this instance (since the
> > constructor doesn't throw, except on bad_alloc).

> Attempting to use this trick to reseat a reference or


> destruct/re-construct const members is UB IIRC, there was a
> thread about this a few months ago I think.

It's not undefined behavior per se. The standard is quite
clear that it is defined, and what its semantics are.

The problem is that its defined semantics can lead to undefined
behavior in so many cases that it's just not worth the bother;
the risk is too great. (And the undefined behavior can appear
as a result of some future evolution in the code.)

With regards to the orginal poster's problem: the answer is
simple: if you want to support assignment, use a pointer, rather
than a reference.

--
James Kanze

James Kanze

unread,
Jun 21, 2010, 6:53:55 PM6/21/10
to
On Jun 11, 10:57 pm, Kai-Uwe Bux <jkherci...@gmx.net> wrote:
> Paul Bibbings wrote:

[...]


> It is undefined behavior according to [3.8/7, item 3], which requires:

> ...
> the type of the original object is not const-qualified, and, if a class
> type, does not contain any non-static data member whose type is const-
> qualified or a reference type,
> ...

I wonder if this text was present in the original version of the
standard. I seem to recall this being legal. (I know that
there was some discussion of it in the committee at the time.)

> Since AType contains a non-static reference member, the trick
> does not work for AType. Note that in the example in the
> standard, there is no non-static reference member.

> I know: it's a bummer since classes with reference members are
> about the only cases, where one might even consider this
> trickery (which is poor form anyway).

The real motivation for the idiom is handling assignment of
virtual base classes correctly. The real problem is that the
idiom doesn't work when inheritance is involved, which makes it
pretty useless for the only real motivation. And of course,
since assignment and inheritance don't work well together
anyway, the fact that assignment is particularly difficult to
get right when virtual base classes are involved isn't really an
issue.

--
James Kanze

Leigh Johnston

unread,
Jun 21, 2010, 7:37:44 PM6/21/10
to
"James Kanze" <james...@gmail.com> wrote in message
news:a28d1a29-e66b-4937...@y4g2000yqy.googlegroups.com...

Wrong, doing it is forbidden by the standard so doing it is undefined
behaviour:

[3.8/7]
"If, after the lifetime of an object has ended and before the storage which

the object occupied is reused or

released, a new object is created at the storage location which the original

object occupied, a pointer that
pointed to the original object, a reference that referred to the original
object, or the name of the original
object will automatically refer to the new object and, once the lifetime of
the new object has started, can

be used to manipulate the new object, if:


the type of the original object is not const-qualified, and, if a class
type, does not contain any non-static

data member whose type is >>>const-qualified<<< or a >>>reference type<<<,
and"

/Leigh

Francesco S. Carta

unread,
Jun 22, 2010, 7:01:43 AM6/22/10
to

Actually, I didn't speculate that much about the uses of my class. We
could eventually consider it something like a read-only, only that it
isn't... read on...

> int i = 1;
> IntByRef ibr(i);
> ibr = 2;
>

Yes, that last line would be forbidden with my class and the compiler
would complain, as if it were read-only - only that the simple
assignment with an actual lvalue would reseat the reference, and that
would make it not read-only - not look like a read-only, that would be
misleading... read on...

> *&ibr3 = 0x22cd08


> */
>
> I think that this would be closer to the behaviour of an ordinary reference over
> all, with the *addition* that it can be reseated. Of course, it also allows for
> the creation of a `reference to a reference':-)

Definitely better, thanks for pointing that out.

It's not that rare to see me posting code out of the top of my head,
just for the sake of explaining my point about something - luckily, some
kindhearted ones happen to pass and fix, extend or somehow else modify
my code and make it something (more) usable :-)

--
FSC
http://userscripts.org/scripts/show/59948

0 new messages