non-const reference and const reference

G

George2

Hello everyone,


This is my understanding of non-const reference, const reference and
their relationships with lvalue/rvalue. Please help to review whether
it is correct and feel free to correct me. Thanks.

1. A const reference can be binded to a rvalue, for example, a
temporary object. And the "life" of the temporary object is guaranteed
to be extended and we can safely operate through the const-reference.

2. A non-const reference can not binded to a rvalue, I think the
reason is rvalue is not addressable? And we can not change the rvalue
through its reference? Are there any other reasons? I am not quite
sure whether my understanding is fully correct. Since there are some
non-modifiable lvalues (so we do not always need to modify values
through its reference). I am still studying what is the reason in
essence in compiler why a non-const reference can not be binded to a
rvalue.

3. Both const and non-const reference can be binded to a lvalue.


thanks in advance,
George
 
A

Alf P. Steinbach

* George2:
Hello everyone,


This is my understanding of non-const reference, const reference and
their relationships with lvalue/rvalue. Please help to review whether
it is correct and feel free to correct me. Thanks.

1. A const reference can be binded to a rvalue, for example, a
temporary object. And the "life" of the temporary object is guaranteed
to be extended and we can safely operate through the const-reference.

Not quite.

Lifetime extension of temporary happens when the reference is declared
in local scope.

It does not happen when you return a reference from a function.


2. A non-const reference can not binded to a rvalue, I think the
reason is rvalue is not addressable?

No, an rvalue can have an address. In particular, you can call member
function on class type rvalues, and you can bind the rvalue to a
reference to const. But note that "rvalue" refers to syntax, how you
denote an object: it's not a property of the object so denoted.

And we can not change the rvalue
through its reference? Are there any other reasons?

Consider

void increment( unsigned& x ) { ++x; }

int main() { int y = 0; increment( unsigned(y) ); }

The cast produces an rvalue. If this compiled it would presumably
increment a temporary instead of y.

Bottom line is by disallowing that binding one avoids many pitfalls.

I am not quite
sure whether my understanding is fully correct. Since there are some
non-modifiable lvalues (so we do not always need to modify values
through its reference). I am still studying what is the reason in
essence in compiler why a non-const reference can not be binded to a
rvalue.

3. Both const and non-const reference can be binded to a lvalue.

Yes.

Cheers, & hth.,

- Alf
 
J

James Kanze

* George2:
Not quite.
Lifetime extension of temporary happens when the reference is
declared in local scope.

Not at all. There are exactly three cases where the temporary
has a different lifetime than the reference: when the reference
is a class member (in that case, lifetime of the temporary is
extended to match the lifetime of the entire constructor, but of
course, the reference itself has the same lifetime as the
constructed object); when the reference is a function argument
(in which case, the temporary naturally has a longer lifetime
than the reference to begin with), and when the reference is a
return value (the temporary only persists until the function
exits).

On the other hand, there's certainly no restriction to local
scope; the lifetime is extended in all other cases.

The usual problem that people have is that the lifetime isn't
extended when a temporary is bound to a reference; the compiler
can't even know, in the general case, if what it is binding is a
temporary. The lifetime is extended when the reference is
*initialized* with a temporary. The difference is a bit subtle;
what it comes down to is that the lifetime is extended *only*
when the initialization expression is a temporary; not when it
is another reference (which might be bound to a temporary). The
best way to think of it in practice, I find, is to consider that
the extension of the lifetime isn't transitive.
It does not happen when you return a reference from a function.

Nor when you initialize a member. On the other hand, it applies
to global references just as much as to local ones.

[...]
void increment( unsigned& x ) { ++x; }
int main() { int y = 0; increment( unsigned(y) ); }
The cast produces an rvalue. If this compiled it would presumably
increment a temporary instead of y.

Not "If this compiled", but "When it was legal C++ and
compiled". The earliest versions of C++ (up to about 1988, I
think) allowed this, with exactly the semantics you suggest.
The restriction was introduced exactly because the rule was so
error prone.
 
A

Alf P. Steinbach

* James Kanze:
Not at all. There are exactly three cases where the temporary
has a different lifetime than the reference: when the reference
is a class member (in that case, lifetime of the temporary is
extended to match the lifetime of the entire constructor, but of
course, the reference itself has the same lifetime as the
constructed object); when the reference is a function argument
(in which case, the temporary naturally has a longer lifetime
than the reference to begin with), and when the reference is a
return value (the temporary only persists until the function
exits).

I hear you say "not at all", then failing to give any counter-example.

On the other hand, there's certainly no restriction to local
scope

That's true.

; the lifetime is extended in all other cases.

Sorry, that's false. You pointed out three cases above, yourself.



[snip]
Nor when you initialize a member. On the other hand, it applies
to global references just as much as to local ones.

That's true.

[...]
void increment( unsigned& x ) { ++x; }
int main() { int y = 0; increment( unsigned(y) ); }
The cast produces an rvalue. If this compiled it would presumably
increment a temporary instead of y.

Not "If this compiled", but "When it was legal C++ and
compiled".

Sorry, that's bull. It makes no sense to talk about "valid" (or
"legal") C++ before standardization.

The earliest versions of C++ (up to about 1988, I
think) allowed this, with exactly the semantics you suggest.
The restriction was introduced exactly because the rule was so
error prone.

In this group we discuss the C++ language.

But yes, it would probably have been even better if I'd expressed the
rules in terms of initialization rather than two examples.


Cheers, & hth.,

- Alf
 
J

James Kanze

* James Kanze:

[...]
I hear you say "not at all", then failing to give any
counter-example.

In the context above, your statement "declared in local scope"
implies, not elsewhere. That may not be what you meant, but it
sure sounded like it.
That's true.
Sorry, that's false. You pointed out three cases above, yourself.

Exactly. All other cases than the ones I mentionned.
[snip]
Nor when you initialize a member. On the other hand, it applies
to global references just as much as to local ones.
That's true.
[...]
Consider
void increment( unsigned& x ) { ++x; }
int main() { int y = 0; increment( unsigned(y) ); }
The cast produces an rvalue. If this compiled it would presumably
increment a temporary instead of y.
Not "If this compiled", but "When it was legal C++ and
compiled".
Sorry, that's bull. It makes no sense to talk about "valid"
(or "legal") C++ before standardization.

You're either being intentionally obtuse, or just playing
stupid. So what was the language in which I wrote valid and
legal programs between 1991 (when I first started working
intensively in C++) and 1998 (when the standard was adopted).

The standardization committee didn't invent the word
C++.

In this case, it's worth pointing out that the original language
did allow initializing a non-const reference with a temporary.
The rule was changed as a result of concrete experience.
In this group we discuss the C++ language.

Which has existed since at least 1986 (the publication date of
The C++ Programming Language).
But yes, it would probably have been even better if I'd
expressed the rules in terms of initialization rather than two
examples.

The key problem I've seen in practice is that people seem to
think that a temporary will last as long as any reference is
bound to it. I personally find that it becomes clearer if the
explination is couched in terms of what happens with the
temporary: if the temporary is used to initialize a reference,
it's lifetime is extended to that of that reference. The point
of view is what happens to the temporary, and not what the
reference was initialized with.

Alternatively, some people seem to understand it better if I
simply state that the extension of lifetime is not transitive;
the fact that reference B is bound to reference A is bound to a
temporary doesn't mean that reference B's lifetime affects the
lifetime of the temporary.

I have no real theories as to why one explination works better
for some people, and the other one for others. Except that
people are different.
 
A

Alf P. Steinbach

* James Kanze:
* James Kanze:
On Dec 15, 6:33 am, "Alf P. Steinbach" <[email protected]> wrote:
[...]
1. A const reference can be binded to a rvalue, for
example, a temporary object. And the "life" of the
temporary object is guaranteed to be extended and we can
safely operate through the const-reference.
Not quite.
Lifetime extension of temporary happens when the reference
is declared in local scope.
Not at all. There are exactly three cases where the
temporary has a different lifetime than the reference: when
the reference is a class member (in that case, lifetime of
the temporary is extended to match the lifetime of the
entire constructor, but of course, the reference itself has
the same lifetime as the constructed object); when the
reference is a function argument (in which case, the
temporary naturally has a longer lifetime than the reference
to begin with), and when the reference is a return value
(the temporary only persists until the function exits).
I hear you say "not at all", then failing to give any
counter-example.

In the context above, your statement "declared in local scope"
implies, not elsewhere. That may not be what you meant, but it
sure sounded like it.
That's true.
Sorry, that's false. You pointed out three cases above, yourself.

Exactly. All other cases than the ones I mentionned.
[snip]
It does not happen when you return a reference from a function.
Nor when you initialize a member. On the other hand, it applies
to global references just as much as to local ones.
That's true.
[...]
Consider
void increment( unsigned& x ) { ++x; }
int main() { int y = 0; increment( unsigned(y) ); }
The cast produces an rvalue. If this compiled it would presumably
increment a temporary instead of y.
Not "If this compiled", but "When it was legal C++ and
compiled".
Sorry, that's bull. It makes no sense to talk about "valid"
(or "legal") C++ before standardization.

You're either being intentionally obtuse, or just playing
stupid.

Are you really sure you want to go that road, James?

First posting a meaningless looks-like-a-clarification, then calling
names when you're taken up on it?

So what was the language in which I wrote valid and
legal programs between 1991 (when I first started working
intensively in C++) and 1998 (when the standard was adopted).

Assuming you used more than one compiler or one compiler with more than
one set of options, the languages you used were not standard C++, and
they were not one language:

they were a number of slightly different languages, conforming in
general to Bjarne Stroustup's "reference manual" for C++.

By (May) 1991 that reference manual had already made the change we're
discussing here.

Programs using the earlier bind-temporary-to-reference-to-non-const
could still compile and in that sense be valid (for a given compiler),
for the intention was to "fade out" use of that feature. So your C++
programs written between 1991 and 1998 could get away with being valid
for the compiler(s) used, but invalid wrt. the "reference manual", and
neither valid nor invalid wrt. to any standard, not yet existing.

Interestingly, while the ARM cites the same rationale as I did earlier
for disallowing the non-const ref binding to temporary, in one Usenet
discussion I had with Bjarne Stroustrup about this, he cited his sense
of elegance and unified rules for allowing temporary lifetime extension,
just one rule fit all (and in particular also fitting argument passing).

So I think there were a number of factors influencing the decision.

The standardization committee didn't invent the word
C++.

In this case, it's worth pointing out that the original language
did allow initializing a non-const reference with a temporary.
The rule was changed as a result of concrete experience.

Yes, it is a good example.

Which has existed since at least 1986 (the publication date of
The C++ Programming Language).


The key problem I've seen in practice is that people seem to
think that a temporary will last as long as any reference is
bound to it. I personally find that it becomes clearer if the
explination is couched in terms of what happens with the
temporary: if the temporary is used to initialize a reference,
it's lifetime is extended to that of that reference.

Sorry, that's not the case, as you have yourself pointed out earlier.

The point
of view is what happens to the temporary, and not what the
reference was initialized with.

Alternatively, some people seem to understand it better if I
simply state that the extension of lifetime is not transitive;
the fact that reference B is bound to reference A is bound to a
temporary doesn't mean that reference B's lifetime affects the
lifetime of the temporary.

I have no real theories as to why one explination works better
for some people, and the other one for others. Except that
people are different.

I think concrete examples work best, as foundation for later
generalization, and/or as clarification of an initial general statement.

At least for this subject there aren't that many really different cases,
so I think examples work fine, but the general rules get hairy --
especially if we get into the rules about copy constructor availability,
current and future.

Cheers, & hth.,

- Alf
 
J

James Kanze

* James Kanze:
[snip]
It does not happen when you return a reference from a function.
Nor when you initialize a member. On the other hand, it applies
to global references just as much as to local ones.
That's true.
[...]
Consider
void increment( unsigned& x ) { ++x; }
int main() { int y = 0; increment( unsigned(y) ); }
The cast produces an rvalue. If this compiled it would presumably
increment a temporary instead of y.
Not "If this compiled", but "When it was legal C++ and
compiled".
Sorry, that's bull. It makes no sense to talk about "valid"
(or "legal") C++ before standardization.
You're either being intentionally obtuse, or just playing
stupid.
Are you really sure you want to go that road, James?
First posting a meaningless looks-like-a-clarification, then calling
names when you're taken up on it?

First, I don't think the clarification was meaningless, because
I've consistently encountered people who misunderstood the issue
when it was presented in that way. And second, the attack is
less ad hominim than a constatation concerning one particular
statement, or way of reasoning. You statement "It makes no
sense to talk about "valid" (or "legal") C++ before
standardization" is simply ridiculous, and I know you know
better. So I have to suppose that you're making it for some
sort of rhetorical reason: being intentionally obtuse, playing
stupid (since I know you're not), or trying to provoke some sort
of reaction I can't see.
Assuming you used more than one compiler or one compiler with
more than one set of options, the languages you used were not
standard C++, and they were not one language:
they were a number of slightly different languages, conforming in
general to Bjarne Stroustup's "reference manual" for C++.

I was there, and the ARM was taken very much as a "standard".
And with the exception of g++ (which really was a different
language at the time), conformance was generally better than
conformance to the ISO standard today. (Note that the chapters
in the ARM on templates and exceptions were considered more or
less experimental, and were not treated as part of the de facto
standard.)
By (May) 1991 that reference manual had already made the
change we're discussing here.

Become a standard? Again, I was there, and there was no ISO
standard until 1998. And the text that would be the standard
didn't really stabilize until 1996-1997. Until well after the
1998, most users considered the ARM as the reference, not the
ISO standard, simply because most compilers implemented
something much closer to the ARM than to the ISO standard.
Programs using the earlier
bind-temporary-to-reference-to-non-const could still compile
and in that sense be valid (for a given compiler), for the
intention was to "fade out" use of that feature.

In 1991, yes. The rule was changed before the ARM, and by 1991,
the ARM was rapidly becoming the definition of what was legal
and valid C++. The rule wasn't present in earlier versions of
"The C++ Programming Language", however, which was the reference
before the ARM.
So your C++ programs written between 1991 and 1998 could get
away with being valid for the compiler(s) used, but invalid
wrt. the "reference manual", and neither valid nor invalid
wrt. to any standard, not yet existing.

A program which bound a temporary to a non-const reference
wasn't valid or legal in 1991. And all of the "up to date"
compilers at the time warned (except maybe g++: at the time, g++
was so bad you couldn't use it). But I'm not sure what your
point is. There was a standard. I evolved over time, some
compilers took more time than others to update, and most treated
the new restrictions as warnings, rather than errors, to avoid
breaking existing code. The situation hasn't really changed,
except that instead of Stroustrup himself promulgating the
standard, it is defined by ISO.

The definition of what is "valid" and "legal" C++ evolves over
time.

Look. I posted because, from experience, I know that the
overall issue needs more clarification than you gave. Everytime
someone hears that "if a temporary is bound to a reference, it's
lifetime is extended to that of the reference", they seem to
assume transitivity (which, strictly speaking, is implied in the
statement, even if it wouldn't occur to me to assume it).

At the end of the statement, I made a minor, unimportant
correction, more in fun than anything else. (Maybe I should
have put a smiley on it.) Technically, it's not "if it
compiles" (since it doesn't), but "when it compiled" (since it
once did). And you come back with this bullshit that there was
no C++ language before ISO. Which, quite frankly, sort of
surprises someone like me who was using it back then; who was
writing C++ code, and sending in error reports to the compiler
vendors for non-conformance with the language specification:
"legal" and "valid" C++ code which didn't compile, or compiled,
but did the wrong thing, and C++ code which should not have
compiled, but did. Of course, we had to contend with the fact
that not all compilers implemented all of the latest
specification (but that's the case today as well), and that some
compilers intentionally deviated in certain cases (also the case
today).
Interestingly, while the ARM cites the same rationale as I did
earlier for disallowing the non-const ref binding to
temporary, in one Usenet discussion I had with Bjarne
Stroustrup about this, he cited his sense of elegance and
unified rules for allowing temporary lifetime extension, just
one rule fit all (and in particular also fitting argument
passing).
So I think there were a number of factors influencing the decision.

As I said, the reason you cited was the reason I'd always heard.
The example you gave is, in fact, the usual example I've seen.
I'm quite convinced that it is the explination of why the change
was made. My critical comment above is limited to your
statement that you couldn't talk about "legal" and "valid" C++
before ISO intervened, when we not only could, but did.
 
A

Alf P. Steinbach

* James Kanze:
* James Kanze:
[snip]
It does not happen when you return a reference from a function.
Nor when you initialize a member. On the other hand, it applies
to global references just as much as to local ones.
That's true.
[...]
Consider
void increment( unsigned& x ) { ++x; }
int main() { int y = 0; increment( unsigned(y) ); }
The cast produces an rvalue. If this compiled it would presumably
increment a temporary instead of y.
Not "If this compiled", but "When it was legal C++ and
compiled".
Sorry, that's bull. It makes no sense to talk about "valid"
(or "legal") C++ before standardization.
You're either being intentionally obtuse, or just playing
stupid.
Are you really sure you want to go that road, James?
First posting a meaningless looks-like-a-clarification, then calling
names when you're taken up on it?

First, I don't think the clarification was meaningless, because
I've consistently encountered people who misunderstood the issue
when it was presented in that way. And second, the attack is
less ad hominim than a constatation concerning one particular
statement, or way of reasoning. You statement "It makes no
sense to talk about "valid" (or "legal") C++ before
standardization" is simply ridiculous, and I know you know
better. So I have to suppose that you're making it for some
sort of rhetorical reason: being intentionally obtuse, playing
stupid (since I know you're not), or trying to provoke some sort
of reaction I can't see.

It really doesn't make sense. Different compilers implemented different
languages. E.g., Borland's Turbo C++ implemented virtual calls from
constructors as they work in Java today, and IIRC Microsoft's Visual C++
(mid-90's) didn't have the exception classes specified by the draft.

I was there, and the ARM was taken very much as a "standard".
And with the exception of g++ (which really was a different
language at the time), conformance was generally better than
conformance to the ISO standard today. (Note that the chapters
in the ARM on templates and exceptions were considered more or
less experimental, and were not treated as part of the de facto
standard.)

Well, there you have it: in order to come with anything resembling a
common well-defined language you have to resort to a subjective view of
which parts of the ARM -- or draft standard -- were significant.

Become a standard?

Tell me, are you reading someone else's articles and answering mine?

Again, I was there, and there was no ISO
standard until 1998. And the text that would be the standard
didn't really stabilize until 1996-1997. Until well after the
1998, most users considered the ARM as the reference, not the
ISO standard, simply because most compilers implemented
something much closer to the ARM than to the ISO standard.

I think that was my point, earlier, but it doesn't hurt repeating. :)

In 1991, yes. The rule was changed before the ARM, and by 1991,
the ARM was rapidly becoming the definition of what was legal
and valid C++. The rule wasn't present in earlier versions of
"The C++ Programming Language", however, which was the reference
before the ARM.


A program which bound a temporary to a non-const reference
wasn't valid or legal in 1991.

Again (but with suitable reinterpretation of "valid" or "legal" in the
sense it seems you're using these words), I think that was *my* point,
in response to your statement implying that you had bound temporaries to
non-const references in programs you made between the years 1991 and
1998, programs that by your definition of "legal" C++ were "legal". Hm.

OK James, it doesn't matter much, this.

I think you're writing late in the evening or something.


[snip]
As I said, the reason you cited was the reason I'd always heard.
The example you gave is, in fact, the usual example I've seen.
I'm quite convinced that it is the explination of why the change
was made. My critical comment above is limited to your
statement that you couldn't talk about "legal" and "valid" C++
before ISO intervened, when we not only could, but did.

Perhaps we're not really disagreeing about anything here except a bit of
terminology.

Cheers, & hth.,

- Alf
 
J

James Kanze

* James Kanze:

[...]
It really doesn't make sense. Different compilers implemented
different languages.

No more so than today. You had a more or less formal
specification (from AT&T, rather than from ISO), a number of
implementations with more or less errors (rather more, back
then, but I don't know of any compiler today without any errors
either), and a few implementations which decided that they knew
better, and intentionally implemented something different (but
less than today---about the only compiler today which even tries
to respect the standard is EDG).
E.g., Borland's Turbo C++ implemented virtual calls from
constructors as they work in Java today, and IIRC Microsoft's
Visual C++ (mid-90's) didn't have the exception classes
specified by the draft.

In the mid-90's, no one (or almost no-one) had exceptions.

As I said, templates and exceptions were more or less considered
experimental extensions, weren't considered part of the
"language", and weren't used if you were trying to be portable.

Again, how does that differ from today? We have a specification
for templates, but only EDG even tries to implement it.
Well, there you have it: in order to come with anything
resembling a common well-defined language you have to resort
to a subjective view of which parts of the ARM -- or draft
standard -- were significant.

Maybe those parts which the author said were significant, and
not experimental? Or simply those parts which consensus had
adopted.

The fact remains that it was easier to write portable C++, which
would be accepted by all compilers, in 1990, than it is today.

[...]
I think that was my point, earlier, but it doesn't hurt
repeating. :)

That may have been what you wanted to say, but it's not what you
said. What you said was that there was no such thing as C++
before the standard. That talking about "legal" and "valid" C++
didn't make sense (although everyone using C++ back then did,
and we knew what we were talking about).

[...]
Again (but with suitable reinterpretation of "valid" or
"legal" in the sense it seems you're using these words), I
think that was *my* point, in response to your statement
implying that you had bound temporaries to non-const
references in programs you made between the years 1991 and
1998, programs that by your definition of "legal" C++ were
"legal". Hm.

Woah. I didn't say that it was "valid" or "legal" in 1991. I
said that there was a time in the past when it was valid and
legal. That time is definitly before 1990: it was valid and
legal when the first edition of "The C++ Programming Language"
appeared, but the rule was changed sometime in the late 1980's.
[snip]
Perhaps we're not really disagreeing about anything here
except a bit of terminology.

Isn't that usually the case with us:). (Except for iostream,
of course.)
 
R

Rahul

Hello everyone,

This is my understanding of non-const reference, const reference and
their relationships with lvalue/rvalue. Please help to review whether
it is correct and feel free to correct me. Thanks.

1. A const reference can be binded to a rvalue, for example, a
temporary object. And the "life" of the temporary object is guaranteed
to be extended and we can safely operate through the const-reference.

2. A non-const reference can not binded to a rvalue, I think the
reason is rvalue is not addressable? And we can not change the rvalue
through its reference? Are there any other reasons? I am not quite
sure whether my understanding is fully correct. Since there are some
non-modifiable lvalues (so we do not always need to modify values
through its reference). I am still studying what is the reason in
essence in compiler why a non-const reference can not be binded to a
rvalue.

3. Both const and non-const reference can be binded to a lvalue.

thanks in advance,
George

References are always const. There is no non-const reference... in
fact reference is a kind of a const pointer which has to be
initialized and once initialized can't be changed or modified...

my compiler also gives me a warning for the following function,

func(const int& const ref)
{
}

indicating tht the second const isn't needed...
 
P

Pete Becker

References are always const. There is no non-const reference...

That's literally true, but people sometimes use "const reference" to
mean "reference to const object". It's imprecise, but usually not
ambiguous or incorrect.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,774
Messages
2,569,596
Members
45,129
Latest member
FastBurnketo
Top