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

X operator=(const &X)

127 views
Skip to first unread message

Ralf Goertz

unread,
Dec 9, 2016, 3:37:36 AM12/9/16
to
Hi,

consider the following program

#include <iostream>

struct X {
const int & cr;
int var;
X(const int &cr_) : cr(cr_),var(0) {}
X child() {
X ret(*this);
ret.var++;
return ret;
}
//X operator=(const X& other) {X ret(*this);return ret;} //(*)
};

int main() {
int k=42;
X x(k);
x=x.child();
std::cout<<x.var<<std::endl;
}


It doesn't compile without the operator definition (*). The error
message is

childtest.cc: In function ‘int main()’:
childtest.cc:18:6: error: use of deleted function ‘X& X::operator=(X&&)’
x=x.child();
^
childtest.cc:3:8: note: ‘X& X::operator=(X&&)’ is implicitly deleted
because the default definition would be ill-formed:

struct X {
^
childtest.cc:3:8: error: non-static reference member ‘const int&
X::cr’, can’t use default assignment operator

When I uncomment (*) it compiles but the program prints „0“ instead of
the expected „1“. There are a few ways to fix this. First, I could make
cr of type „int“, so no reference and non-const. That would work. But in
the real world X is a heavy type and I need to x=x.child() all the time
in a recursive function. That would mean a waste of space and
performance. Also

X y=x.child()

and outputting y also works correctly without (*)

By the way, this was compiled using -std=c++11, but the problem is also
present without it. The only difference is it complaints about X&
instead of X&& in the deleted operator. What is going on and how can I
fix this?

Thanks

Öö Tiib

unread,
Dec 9, 2016, 4:26:34 AM12/9/16
to
On Friday, 9 December 2016 10:37:36 UTC+2, Ralf Goertz wrote:
> Hi,
>
> consider the following program
>
> #include <iostream>
>
> struct X {
> const int & cr;
> int var;
> X(const int &cr_) : cr(cr_),var(0) {}
> X child() {
> X ret(*this);
> ret.var++;
> return ret;
> }
> //X operator=(const X& other) {X ret(*this);return ret;} //(*)

Note that here is rather unusual assignment operator commented
out that does not modify 'this' object at all (IOW does not assign).
May be it is the source of your self-confusion?

> };
>
> int main() {
> int k=42;
> X x(k);
> x=x.child();
> std::cout<<x.var<<std::endl;
> }
>
>
> It doesn't compile without the operator definition (*).

How you want compiler to generate copy or move assignment?
Your class has reference member. References are immutable after
declaration so can not be neither copied nor moved to already
existing reference.

> The error message is

Basically saying exactly what what I said in lot better English and
more detail than I can produce.

> When I uncomment (*) it compiles but the program prints „0“ instead of
> the expected „1“.

The output "0" is what I would expect from the program
posted and with (*) uncommented. Can you tell why you expect
"1"?

Alf P. Steinbach

unread,
Dec 9, 2016, 4:36:02 AM12/9/16
to
On 09.12.2016 09:37, Ralf Goertz wrote:
>
> childtest.cc: In function ‘int main()’:
> childtest.cc:18:6: error: use of deleted function ‘X& X::operator=(X&&)’
> x=x.child();
> ^
> childtest.cc:3:8: note: ‘X& X::operator=(X&&)’ is implicitly deleted
> because the default definition would be ill-formed:
>
> struct X {
> ^
> childtest.cc:3:8: error: non-static reference member ‘const int&
> X::cr’, can’t use default assignment operator

And?

Isn't that clear?


Cheers!,

- Alf


Ralf Goertz

unread,
Dec 9, 2016, 4:50:18 AM12/9/16
to
Am Fri, 9 Dec 2016 10:32:33 +0100
schrieb "Alf P. Steinbach" <alf.p.stein...@gmail.com>:
It is clear, that's why I tried to define my own assignment operator,
but I obviously failed. What would be the correct way to do it when I
really want cr to be a constant reference?

Ralf Goertz

unread,
Dec 9, 2016, 4:52:25 AM12/9/16
to
Am Fri, 9 Dec 2016 01:26:25 -0800 (PST)
schrieb Öö Tiib <oot...@hot.ee>:

> On Friday, 9 December 2016 10:37:36 UTC+2, Ralf Goertz wrote:
> > Hi,
> >
> > consider the following program
> >
> > #include <iostream>
> >
> > struct X {
> > const int & cr;
> > int var;
> > X(const int &cr_) : cr(cr_),var(0) {}
> > X child() {
> > X ret(*this);
> > ret.var++;
> > return ret;
> > }
> > //X operator=(const X& other) {X ret(*this);return ret;} //(*)
>
> Note that here is rather unusual assignment operator commented
> out that does not modify 'this' object at all (IOW does not assign).
> May be it is the source of your self-confusion?

Yep that seems to be the problem. What would be the correct definition?
This definition

X operator=(const X& other) { cr=other.cr;var=other.var}

doesn't work either.

Paavo Helde

unread,
Dec 9, 2016, 5:00:44 AM12/9/16
to
On 9.12.2016 11:50, Ralf Goertz wrote:
> Am Fri, 9 Dec 2016 10:32:33 +0100

> It is clear, that's why I tried to define my own assignment operator,
> but I obviously failed. What would be the correct way to do it when I
> really want cr to be a constant reference?
>

#include <iostream>

struct X {
const int & cr;
int var;
X(const int &cr_): cr(cr_), var(0) {}
X child() {
X ret(*this);
ret.var++;
return ret;
}
X& operator=(const X& other) {
this->~X();
new (this) X(other);
return *this;
}
};

int main() {
int k = 42;
X x(k);
x = x.child();

Paavo Helde

unread,
Dec 9, 2016, 5:06:18 AM12/9/16
to
On 9.12.2016 12:00, Paavo Helde wrote:
> X& operator=(const X& other) {
> this->~X();
> new (this) X(other);
> return *this;
> }

Correction:

X& operator=(const X& other) {
if (this!=&other) {

Ralf Goertz

unread,
Dec 9, 2016, 5:09:35 AM12/9/16
to
Am Fri, 09 Dec 2016 12:00:34 +0200
schrieb Paavo Helde <myfir...@osa.pri.ee>:

> X& operator=(const X& other) {
> this->~X();
> new (this) X(other);
> return *this;
> }

Thanks, that seems to work. Do I need to worry about memory leakage
since I use new without delete?

Öö Tiib

unread,
Dec 9, 2016, 5:51:02 AM12/9/16
to
That code contains "placement new" not usual "new". It does not allocate
memory but instead uses memory that was given to it with argument.

Alain Ketterlin

unread,
Dec 9, 2016, 7:58:49 AM12/9/16
to
There is no memory allocation here, this can't cause leakage.

But you need to worry about your design: defining a struct with a const
ref member *and* an assignment operator does not make much sense.
Paavo's solution essentially bypasses the inherent incoherence by using
destructor and constructor, but the problem remains: your design is
absurd.

-- Alain.

Ralf Goertz

unread,
Dec 9, 2016, 9:24:07 AM12/9/16
to
Am Fri, 09 Dec 2016 13:58:38 +0100
schrieb Alain Ketterlin <al...@universite-de-strasbourg.fr.invalid>:

> But you need to worry about your design: defining a struct with a
> const ref member *and* an assignment operator does not make much
> sense. Paavo's solution essentially bypasses the inherent incoherence
> by using destructor and constructor, but the problem remains: your
> design is absurd.

Okay, I am open to suggestions as to the design, although with Paavo's
solution, everything works as expected. My X is a class used in a
generic backtracking algorithm I have written. It contains the status
that changes when creating new chlidren or siblings.

template <class Status> class Backtrack {
bool findAll, isValid;
Status state;
bool reject();
bool accept();
Backtrack<Status> first();
Backtrack<Status> next();
void output();
public:
Backtrack(Status initial, bool fa=false) :
findAll(fa),state(initial),isValid(true) {}
void bt() {
//code according to the pseudo code in the wikipedia
//article on backtracking
}
};

I've used that code in a number of projects now by writing appropriate
functions reject(), accept(), first(), next(), and output(). It always
bothered me that I couldn't have members of Status which are const &,
although it might contain immutable information that is needed at all
levels of the search tree. My attempt is not to expose that information
if it is not necessary. Therefore, I want to avoid a global variable.
Also, a static member variable feels wrong since it must be accessed
from outside the class and I want to be able to have more than one
variable of class Backtrack<X> with different information in state. With
my design now I only need my „Special“ Status class and

Backtrack<Special> b;
b.bt(Special(…),true);

and I can use different parameters in … As I said, „Special“ can be very
big with only a few bits of information actually changing. Therefore, I
try to only make those parts variable and keep the rest const & as in
the example in the OP to avoid copying all the big stuff.

Any comments?

Paavo Helde

unread,
Dec 9, 2016, 9:30:00 AM12/9/16
to
No, there is no leak because there is no actual memory allocation involved.

However, you need to worry about self-assignment (fixed in my followup
post) and also you need to worry about exception throwing in the
constructor and destructor. If they happen to throw, then the object may
remain in an inconsistent/unusable state. In your simple example there
is no possibility of exceptions, but in real life this is something to
consider, and one might need to add some try-catch block and restore the
object into some valid fallback state.

In situations like that I have just used a pointer instead of the
reference, all this hassle is just not worth it.

Cheers
Paavo



Paavo Helde

unread,
Dec 9, 2016, 10:08:19 AM12/9/16
to
On 9.12.2016 16:23, Ralf Goertz wrote:
> As I said, „Special“ can be very
> big with only a few bits of information actually changing. Therefore, I
> try to only make those parts variable and keep the rest const & as in
> the example in the OP to avoid copying all the big stuff.

My solution basically works around this design and actually changes the
const references. If this is not what you wanted, then you should just
simply define an assignment operator which only changes the needed pieces:

#include <iostream>

struct X {
const int & cr;
int var;
X(const int &cr_): cr(cr_), var(0) {}
X child() {
X ret(*this);
ret.var++;
return ret;
}
X& operator=(const X& other) {
var = other.var;

Ralf Goertz

unread,
Dec 9, 2016, 10:37:35 AM12/9/16
to
Am Fri, 09 Dec 2016 17:08:10 +0200
schrieb Paavo Helde <myfir...@osa.pri.ee>:

> My solution basically works around this design and actually changes
> the const references. If this is not what you wanted, then you should
> just simply define an assignment operator which only changes the
> needed pieces:
>
>
> X& operator=(const X& other) {
> var = other.var;
> return *this;
> }

Hm, how come that this->cr is still k? Just because the left side of =
must have existed before? But then this would be some weired assignment,
wouldn't it?

int k(42), j(4711);
X x(k), y(j);
x=y;

Now x.cr would still be k, right?


Paavo Helde

unread,
Dec 9, 2016, 10:47:04 AM12/9/16
to
On 9.12.2016 17:37, Ralf Goertz wrote:
> Am Fri, 09 Dec 2016 17:08:10 +0200
> schrieb Paavo Helde <myfir...@osa.pri.ee>:
>
>> My solution basically works around this design and actually changes
>> the const references. If this is not what you wanted, then you should
>> just simply define an assignment operator which only changes the
>> needed pieces:
>>
>>
>> X& operator=(const X& other) {
>> var = other.var;
>> return *this;
>> }
>
> Hm, how come that this->cr is still k? Just because the left side of =
> must have existed before?

Assignment indeed means that the assigned object already exists before.
If it does not, then you have a constructor, not assignment.

> But then this would be some weired assignment,
> wouldn't it?
>
> int k(42), j(4711);
> X x(k), y(j);
> x=y;
>
> Now x.cr would still be k, right?

Right.

The assignment is weird because references cannot be reseated. I thought
this is what you wanted ("keep the rest const [...] to avoid copying all
the big stuff").

The destructor+placement new trick works around this and actually
reseats the references. It is up to you to decide which behavior you need.

Cheers
Paavo





ruben safir

unread,
Dec 10, 2016, 11:17:54 AM12/10/16
to
On 12/09/2016 03:37 AM, Ralf Goertz wrote:
> X(const int &cr_) : cr(cr_),var(0) {}

what is this?

Öö Tiib

unread,
Dec 10, 2016, 12:13:39 PM12/10/16
to
It is called implicit conversion constructor from int to X.
Experienced C++ programmers tend to avoid implicit conversions
like the plague.

Richard Damon

unread,
Dec 10, 2016, 4:41:40 PM12/10/16
to
Since you are using placement new, it isn't going to create a memory
leak, as placement new doesn't itself allocate any memory.

Note, that this is an old hack for reseating a reference, and one with a
number of know dangers.

First, you need to check for self assignment, that will blow up with
your code.

Second, you may never derive a class from X now (or at least any class
derived from X can't chain to this operator=, unlike the normal, and
default, assignment operators).

Ralf Goertz

unread,
Dec 11, 2016, 5:13:28 AM12/11/16
to
Am Sat, 10 Dec 2016 09:13:26 -0800 (PST)
schrieb Öö Tiib <oot...@hot.ee>:
It might look like an implicit conversion constructor but actually it is
meant to be a regular contructor. In the context of my original question
it is clear why I need this constructor. How else am I supposed to
initialise a constant reference cr in my class?

ruben safir

unread,
Dec 11, 2016, 5:31:19 AM12/11/16
to
On 12/10/2016 12:13 PM, Öö Tiib wrote:
> implicit conversion constructor from int to X


thanks

Bo Persson

unread,
Dec 11, 2016, 5:51:50 AM12/11/16
to
And also, in case the new throws an exception we will end up with a
destructed object that isn't reconstructed. Bad things will follow.


Bo Persson

Paavo Helde

unread,
Dec 11, 2016, 8:02:53 AM12/11/16
to
What they are trying to say is that such constructors should be declared
with the 'explicit' keyword.



Ralf Goertz

unread,
Dec 13, 2016, 3:06:38 AM12/13/16
to
Am Sun, 11 Dec 2016 15:02:47 +0200
schrieb Paavo Helde <myfir...@osa.pri.ee>:
That's what I thought at first, too but then how can this be a
satisfactory answer to the question

What is 'X(const int &cr_) : cr(cr_),var(0) {}'?

Anyone who knows what

explicit X(const int &cr_) : cr(cr_),var(0) {}

means, surely knows what to make of that statement without the
„explicit“ keyword. And secondly, I was giving a minimal example of my
problem with the *assignment* operator. Having the constructor made
explicit wouldn't have changed anything and would have made the code
longer (less minimal ;-)). Therefore I assumed Öö had something else in
mind.

Paavo Helde

unread,
Dec 13, 2016, 5:09:11 AM12/13/16
to
On 13.12.2016 10:07, Ralf Goertz wrote:
> Am Sun, 11 Dec 2016 15:02:47 +0200
> schrieb Paavo Helde <myfir...@osa.pri.ee>:
>
>> On 11.12.2016 12:13, Ralf Goertz wrote:
>>> Am Sat, 10 Dec 2016 09:13:26 -0800 (PST)
>>> schrieb Öö Tiib <oot...@hot.ee>:
>>>
>>>> On Saturday, 10 December 2016 18:17:54 UTC+2, ruben safir wrote:
>>>>> On 12/09/2016 03:37 AM, Ralf Goertz wrote:
>>>>>> X(const int &cr_) : cr(cr_),var(0) {}
>>>>>
>>>>> what is this?
>>>>
>>>> It is called implicit conversion constructor from int to X.
>>>> Experienced C++ programmers tend to avoid implicit conversions
>>>> like the plague.
>>>
>>> It might look like an implicit conversion constructor but actually
>>> it is meant to be a regular contructor. In the context of my
>>> original question it is clear why I need this constructor. How else
>>> am I supposed to initialise a constant reference cr in my class?
>>
>> What they are trying to say is that such constructors should be
>> declared with the 'explicit' keyword.
>
> That's what I thought at first, too but then how can this be a
> satisfactory answer to the question
>
> What is 'X(const int &cr_) : cr(cr_),var(0) {}'?

I guess this was not an answer to the original question, but rather
another remark which the poster considered worthwhile to share.

>
> Anyone who knows what
>
> explicit X(const int &cr_) : cr(cr_),var(0) {}
>
> means, surely knows what to make of that statement without the
> „explicit“ keyword. And secondly, I was giving a minimal example of my
> problem with the *assignment* operator. Having the constructor made
> explicit wouldn't have changed anything and would have made the code
> longer (less minimal ;-)). Therefore I assumed Öö had something else in
> mind.

Welcome to Usenet! Longest threads covering philosophy, religion, etc
often start from some silly question like how many spaces a tab must be.

Cheers
Paavo


Juha Nieminen

unread,
Dec 13, 2016, 8:16:43 AM12/13/16
to
That looks like a really ugly way of getting around the
non-assignability of references. Is it even supported by the standard?

Paavo Helde

unread,
Dec 13, 2016, 8:49:21 AM12/13/16
to
Yes, it is supported. There is even a verbatim example featuring the
very same "operator=" and "new (this)". See 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 ..."

I snipped some sanity checks like requiring same type and most derived
object etc. One must also take care with exceptions as discussed
else-thread.

Cheers
Paavo

PS. I agree this is ugly and error-prone and one should not use this
unless there is no other way.

Melzzzzz

unread,
Dec 13, 2016, 9:05:53 AM12/13/16
to
How would you check that?

One must also take care with exceptions as discussed
> else-thread.
>
> Cheers
> Paavo
>
> PS. I agree this is ugly and error-prone and one should not use this
> unless there is no other way.
>


--
press any key to continue or any other to quit...

Öö Tiib

unread,
Dec 13, 2016, 11:03:06 AM12/13/16
to
On Tuesday, 13 December 2016 16:05:53 UTC+2, Melzzzzz wrote:
> On 2016-12-13, Paavo Helde <myfir...@osa.pri.ee> wrote:

<snip>

> > I snipped some sanity checks like requiring same type and most derived
> > object etc.
>
> How would you check that?

C++ has whole whopping RTTI available. It can be very useful in
debug checks. Example teaser program:

#include <iostream>
#include <typeinfo>

// base
struct B
{
B() = default;

virtual ~B() = default; // something virtual to turn on RTTI

B& operator=(const B& that) // assignment we test
{
// just to visualize it; object slicing actually
// deserves major *ABORT* here.
std::cout << (typeid(*this) != typeid(that) ? "wtf\n"
: "ok\n");
return *this;
};
B (B const&) = delete; // because of rule of 3
};

// derived
struct D : public B {};

int main()
{
B b;
B b2;
b = b2; // ok
D d;
b = d; // wtf
B& dRef{d};
dRef = b; // wtf
D d2;
dRef = d2; // ok
}

Paavo Helde

unread,
Dec 13, 2016, 12:57:50 PM12/13/16
to
By "sanity checks" I actually meant the numerous clauses written in the
standard in the "if ..." section after the above quote. But one can
protect the code against some misusage also automatically. The simplest
way to ensure that the object is the most derived object is do declare
the class as 'final'.

Cheers
Paavo




Melzzzzz

unread,
Dec 13, 2016, 1:01:49 PM12/13/16
to
What about multiple inheritance? Is it ok then to assign d to d?

Melzzzzz

unread,
Dec 13, 2016, 1:03:06 PM12/13/16
to
On Tue, 13 Dec 2016 19:57:38 +0200
Hm, we now have final? ;)

Didn't knew that!

Öö Tiib

unread,
Dec 13, 2016, 1:49:58 PM12/13/16
to
There are no problems if assignments are implemented correctly.
Note that above example was not about correct implementation of
assignment. Classes were stateless so there was nothing to assign.
It was demonstration how typeid works with polymorphic types.

The last assignment in example was even "mishandled" by the
program. It was declared "ok" despite it was actually "slicing
assignment" (it used B::operator= to assign D to D). ;)

Popping mad

unread,
Dec 13, 2016, 2:59:29 PM12/13/16
to
On Tue, 13 Dec 2016 12:08:53 +0200, Paavo Helde wrote:

> Welcome to Usenet! Longest threads covering philosophy, religion, etc
> often start from some silly question like how many spaces a tab must be.

3

or

42

Tim Rentsch

unread,
Dec 13, 2016, 5:38:38 PM12/13/16
to
> derived object etc. [...]

One of those checks says

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

Doesn't this requirement rule out the example being asked about,
which had a non-static data member of reference type?

Öö Tiib

unread,
Dec 14, 2016, 5:15:59 AM12/14/16
to
Yes, good catch, compiler may assume that reference member can not be
redirected. So it may cache it somewhere as optimization to reduce
indirection level and then use cached value after assignment and
program does not work.

People who want to avoid raw pointers but need references that can be
modified should perhaps use things made for it like 'std::weak_ptr' or
'std::reference_wrapper'.

Paavo Helde

unread,
Dec 14, 2016, 7:34:14 AM12/14/16
to
I stand corrected - it seems I had overlooked this clause. But now it
seems to me that this clause is forbidding the only usage scenarios
where this trick would make sense at all. If there are no const members
or references then everything can be done by a normal assignment
operator, can't it?

Öö Tiib

unread,
Dec 14, 2016, 9:43:40 AM12/14/16
to
What is normal assignment operator? You mean compiler-generated one?
Members of not copyable types without const or reference involved
(like std::unique_ptr or std::mutex) don't let compiler to generate
copy assignment operators. So trick may be still useful.

Tim Rentsch

unread,
Dec 14, 2016, 7:42:08 PM12/14/16
to
It looks to me like normal assignment can do the job, yes.
Although it might be more convenient, code-wise, to use an
in-place constructor than to simulate what the constructor
would do.
0 new messages