Topic: Passing temporary variables by reference.
Author: Richard See <Richard.See@vega.co.uk>
Date: 1997/06/05 Raw View
(A pity I didn't spot Brian's earlier post to this news group,
"reference conversion operator query",
which is a strongly related topic, before making my original post.)
Brian Parker wrote:
...
> A small extension to a suggestion I have made previously would be
> useful solution: I think that the draft standard should allow
> operatorT&( ) (which returns an lvalue) to be implicitly called when
> binding a temporary (i.e. rvalue) to a non-const reference.
> At present, Sec 8.5.3 [dcl.init.ref] paragraph 6 unnecessarily
> restricts its use to the binding of lvalues (and 12.3.2
> [class.conv.fct] paragraph 1 disallows it when returning a reference
> of the same type as the class itself).
>
> In your case, this would allow you to add operator ofstream& ( )
> {return *this;} to your ofstream class
...
(The test code at the bottom of this post is arguably more interesting
than the text which follows).
Applying your idea would be an interesting solution. I like it, except
for one concern: it still leaves an inconsistency whereby
ofstream("a_file") << t;
may or may not compile, depending on whether operator << is defined as a
member operator or as a global operator for this particular type.
(Global functions must take const& to temporary, whilst non-const member
functions can be called on a temporary.)
If consistency here is an aim (I'm happy to sacrifice consitency for
usability if there's a trade off, tho I hope there isn't), then only two
possibilities occur to me:
(a) global functions/operators can take a temporary by non-const
reference (at least in the case above - I'm still interested to hear if
it's possible to discriminate between different uses of temporaries).
(b) non-const member functions cannot be called on temporaries.
In case (a), the problem has vanished anyway. In case (b), I would have
a problem with constness:
operator T& () const { return *const_cast<T*>(this); }
(or something like that) would be needed within my stream class (of type
T), and in any class that uses reference semantics, such as your proxy
class for your array class (related thread, "reference conversion
operator query").
I strongly feel that the constness (or otherwise) of temporaries is the
key issue here (or more accurately, that it's the only issue that
appears neglected).
(At this point, I was suddenly hit by a realisation of my lack of
understanding of the relationship between (lvalues and rvalues) and
(non-const and const). I still don't understand this, but did manage to
produce some rather odd test code which undermines my belief that being
able to call non-const member functions on temporaries is consistent
with them being rvalues. This is unfortunate, as it would cause the
problems mentioned in (b) above.)
I worry over this code (tested with Borland C++ 5.01 and Borland C++
5.02):
class C1 {
public:
const C1& operator = (const C1&) { return *this; }
};
class C2 {};
C1 f1(void) { return C1(); }
C2 f2(void) { return C2(); }
void f(void) {
C1 c1;
C2 c2;
f1() = c1; // fine, even for rvalue as non-const member function
called.
f2() = c2; // error, can't assign to rvalue.
}
I don't know if this is the correct behaviour according to the draft
standard, but I rather suspect it is. I'm going to go away and think a
bit before I write any more.
---
[ comp.std.c++ is moderated. To submit articles: Try just posting with your
newsreader. If that fails, use mailto:std-c++@ncar.ucar.edu
comp.std.c++ FAQ: http://reality.sgi.com/austern/std-c++/faq.html
Moderation policy: http://reality.sgi.com/austern/std-c++/policy.html
Comments? mailto:std-c++-request@ncar.ucar.edu
]
Author: jpotter@falcon.lhup.edu (John Potter)
Date: 1997/06/06 Raw View
On 05 Jun 1997 16:00:15 PDT, Richard See <Richard.See@vega.co.uk>
wrote:
: (A pity I didn't spot Brian's earlier post to this news group,
: "reference conversion operator query",
: which is a strongly related topic, before making my original post.)
: Brian Parker wrote:
: ...
: > A small extension to a suggestion I have made previously would be
: > useful solution: I think that the draft standard should allow
: > operatorT&( ) (which returns an lvalue) to be implicitly called when
: > binding a temporary (i.e. rvalue) to a non-const reference.
[...]
: > In your case, this would allow you to add operator ofstream& ( )
: > {return *this;} to your ofstream class
: ...
: (The test code at the bottom of this post is arguably more interesting
: than the text which follows).
Not really, it is just a compiler bug.
: Applying your idea would be an interesting solution. I like it, except
: for one concern: it still leaves an inconsistency whereby
: ofstream("a_file") << t;
: may or may not compile, depending on whether operator << is defined as a
: member operator or as a global operator for this particular type.
: (Global functions must take const& to temporary, whilst non-const member
: functions can be called on a temporary.)
His point is that it would not need to take a const& since the
operator T& would be used to generate a nonconst&.
I guess that I put myself on the side of those who do not like the
implementation to do too many things without being explicitely told to
do so. There have been requests to make it possible to declare
conversion operators explicit just like conversion constructors.
Implicit conversions are considered bad by many. It is not needed
since you can use a named function to do it rather than an operator.
The status of class rvalues is a bit confusing since they are objects
unlike builtin rvalues. You may bind an rvalue to a const reference
and may modify an rvalue via a member function (which means the
implementation can create a nonconst pointer to an rvalue even though
you may not take the address of an rvalue). The whole idea is to
prevent surprises. I like the way things are now, because I can
explicitely say that I wish to modify an rvalue and get an lvalue with
which to do it. I don't want the implementation doing it unless I ask
for it.
template <class T>
T& lvalue (T const& t) {
return const_cast<T&>(t);
}
Now your problem is solved explicitely
lvalue(ofstream("a_file")) << t;
works for either member or global. And, YOU must say it.
: If consistency here is an aim (I'm happy to sacrifice consitency for
: usability if there's a trade off, tho I hope there isn't), then only two
: possibilities occur to me:
: (a) global functions/operators can take a temporary by non-const
: reference (at least in the case above - I'm still interested to hear if
: it's possible to discriminate between different uses of temporaries).
The programmer can and should. Keep the implementation dumb.
The next line says it, the issue is rvalue not const. A const& is a
nonmodifiable lvalue. A temporary is an rvalue.
: (At this point, I was suddenly hit by a realisation of my lack of
: understanding of the relationship between (lvalues and rvalues) and
: (non-const and const). I still don't understand this, but did manage to
: produce some rather odd test code which undermines my belief that being
: able to call non-const member functions on temporaries is consistent
: with them being rvalues. This is unfortunate, as it would cause the
: problems mentioned in (b) above.)
: I worry over this code (tested with Borland C++ 5.01 and Borland C++
: 5.02):
: class C1 {
: public:
: const C1& operator = (const C1&) { return *this; }
: };
: class C2 {};
Implementation must now declare within your class
public :
inline C2 ();
inline C2 (C2 const&);
inline ~C2 ();
inline C2& operator= (C2 const&);
: C1 f1(void) { return C1(); }
: C2 f2(void) { return C2(); }
Implementation must now define C2 () and C2 (C2 const&) since you have
used them.
: void f(void) {
: C1 c1;
: C2 c2;
Implementation must now define ~C2 () since you have required it.
: f1() = c1; // fine, even for rvalue as non-const member function
: called.
: f2() = c2; // error, can't assign to rvalue.
Implementation must now define C2& operator= (C2 const&), since you
have used it.
That is not how it was in the ARM but it is in the CD. You are using
a member function. An operator= function must be a member function.
The builtin symbol = requires an lvalue on the left but it can not be
used with user types. This is a behind the draft problem with BC and
many others. The ARM said that these things would be defined but not
how and it was common to treat it as the global operator which works
for struct in C. The draft explicitely states that they will be
defined as public inline member functions.
: }
John
---
[ comp.std.c++ is moderated. To submit articles: Try just posting with your
newsreader. If that fails, use mailto:std-c++@ncar.ucar.edu
comp.std.c++ FAQ: http://reality.sgi.com/austern/std-c++/faq.html
Moderation policy: http://reality.sgi.com/austern/std-c++/policy.html
Comments? mailto:std-c++-request@ncar.ucar.edu
]
Author: Richard See <Richard.See@vega.co.uk>
Date: 1997/06/07 Raw View
I'll try to keep this one as a short note in the form:
"I don't understand why C++ was designed the way it was. I can see how
it's supposed to work in this case, but the language definition seems
wrong." - moderation policy.
Naturally, I've included a description of what seems right to me as
well. Had I not done that, it wouldn't have been possible for replies
to explain any deficiencies in my thinking.
(I failed - it's a rather long note in said form.)
Part A:
I don't understand why it should be permissable to call a non-const
member function on a temporary, but not to use a temporary as the
argument to a function that takes a non-const reference. This appears
to me to be inconsistent.
(This inconsistency is most apparent in the case of operators, where
member function calls and global function calls have the same syntax.
It is also apparent with functions overloaded by constness. In a global
function call on a temporary, the function taking the argument as a
const reference will be called. In a member function call on a
temporary, the non-const function will be called unless the temporary
was explicitly created as const. This may lead to unexpected results.)
Question A:
If it is deemed vital that the compiler prevents the programmer from
accidentally modifying a temporary with a call to a global function, why
isn't it deemed vital that the compiler prevents the programmer from
accidentally modifying a temporary with a call to a member function?
Part B:
Normally, the programmer can specify whether or not the compiler should
allow an object to be altered by specifying the constness of an object.
Constness is a "new" feature (compared to K&R C). Built in operations
on built in types have never made use of the const language feature for
specifying what operations can be performed on their return value, but
instead rely on rules on what operations can be performed on an rvalue.
Return values of user defined type cannot rely solely on the rvalue
concept to avoid being modified, due to the ability to call a non-const
member function on a non-const rvalue. Writers of functions that return
temporaries of user defined type must use constness to supplement the
properties of rvalues (for example making the return value of operator
++(int) const, see Meyers, More Effective C++, Item 6) if they wish to
be sure of returned temporaries not being accidentally modified.
Question B1:
Since the const language feature is available to the programmer to
specify whether or not a temporary can be altered by a member function
call (for a user defined type), why can this language feature not be
used to specify whether temporaries can be altered by a global function
call (for any type)?
Question B2:
Why was the ability described in question B1 removed by adding the
"no binding temporaries to non-const references" rule, instead of making
compiler generated operations that return temporaries that should not be
altered return const temporaries, and encouraging programmers to do the
same (as they have to still for user defined types anyway)?
Notes B:
This would not prevent the compiler from generating a "code has no
effect" warning for pointless operations on non-const temporaries of
built in type.
Constness cannot be specified where a compiler generates a temporary
implicitly, for example for a cast operation using a constructor call
rather than a cast operator call. Assuming const here would not
generate conflicts with the existing specification, because (a) it isn't
possible to write code that calls a member function directly on an
implicitly generated temporary and (b) global functions cannot currently
take implicitly generated temporaries by non-const reference.
There may be other places where it isn't possible to specify the
constness of temporaries, I'm assuming this argument holds there too.
---
[ comp.std.c++ is moderated. To submit articles: Try just posting with your
newsreader. If that fails, use mailto:std-c++@ncar.ucar.edu
comp.std.c++ FAQ: http://reality.sgi.com/austern/std-c++/faq.html
Moderation policy: http://reality.sgi.com/austern/std-c++/policy.html
Comments? mailto:std-c++-request@ncar.ucar.edu
]
Author: James Kanze <james-albert.kanze@vx.cit.alcatel.fr>
Date: 1997/06/09 Raw View
Richard See <Richard.See@vega.co.uk> writes:
|> Question A:
|>
|> If it is deemed vital that the compiler prevents the programmer from
|> accidentally modifying a temporary with a call to a global function, why
|> isn't it deemed vital that the compiler prevents the programmer from
|> accidentally modifying a temporary with a call to a member function?
Because the temporaries tend to crop up without the programmer expecting
them: passing an unsigned to a function expecting an "int&", for
example. No type conversions will be used to call a member function;
the programmer must explicitly call the member function on what is
visibly a temporary.
Some discussion has occured on loosening the rule, according to the
source of the temporary. (Basically, inhibit type conversions when
binding to a non-const.) As far as I know, however, no one ever came up
with a valid proposal concerning this for the standards committee to
consider. The current rule IS awkward, but no one has made a concrete
proposal of something that is better.
--
James Kanze home: kanze@gabi-soft.fr +33 (0)1 39 55 85 62
office: kanze@vx.cit.alcatel.fr +33 (0)1 69 63 14 54
GABI Software, Sarl., 22 rue Jacques-Lemercier, F-78000 Versailles France
-- Conseils en informatique industrielle --
---
[ 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 ]
[ FAQ: http://reality.sgi.com/employees/austern_mti/std-c++/faq.html ]
[ Policy: http://reality.sgi.com/employees/austern_mti/std-c++/policy.html ]
[ Comments? mailto:std-c++-request@ncar.ucar.edu ]
Author: Richard See <Richard.See@vega.co.uk>
Date: 1997/06/03 Raw View
Apologies for the way this gets clearer towards the end of the post.
Guess I hadn't finished thinking when I started writing.
Borland C++ 5.02 describes a new language rule (in its readme.txt file):
>>
>> The new 32-bit compiler implements a language requirement that may
>> break some existing code. The new rule is that you cannot pass a
>> temporary variable by reference--you must pass it by const reference.
>>
>> For example, given a function with this prototype:
>>
>> func1 (TMyClass& o);
>>
>> the following call used to be acceptable:
>>
>> func1 ( TMyClass() );
>>
>> However, you must now change the call to:
>>
>> func1(TMyClass const & 0)
>>
I find this does indeed break plenty of existing code. It leads to
problems (an error and arguably an inconsistency) in the following code:
#include <fstream>
class ExampleClass {};
ostream& operator <<(ostream&s, const ExampleClass&) { return s; }
void f(void) {
int i = 2;
ExampleClass e;
// call non-const member operator on temporary.
ofstream("a_file") << i; // BC5.02 compiles this line
// call operator with temporary as argument:
ofstream("b_file") << e; // BC5.02 doesn't compile this line
}
Is this the intended effect, and is Borland's interpretation of the rule
correct?
My personal expectation would have been for a rule along the lines of
... cannot pass a _compiler_generated_ temporary by reference ...
although I've only been thinking about this for a couple of hours, and
my judgement is probably clouded by the thought of having to rewrite
lots of code, and I've no idea how easy/difficult it is to define
"compiler generated", nor to distinguish between a compiler generated
temporary and one that the user has supplied.
This would at least catch errors like this:
void increment(int& i) {
++i;
}
void f(void) {
float f = 2;
increment(f); // compiler generated temporary - error.
}
and yet still allow the user to generate a temporary if he's sure that's
the right thing to do.
Alternatively, should there be (maybe there already is) a rule that says
non-const member functions cannot be called on temporary variables?
This would at least be consistent - for example the line
ofstream("a_file") << i;
in the first example wouldn't compile either. Needless to say, this
wouldn't be my preference.
Another way of looking at it is in terms of the constness of
temporaries. If temporaries had constness defined (all temporaries are
const would cover the previous example, or compiler generated
temporaries are const and user generated temporaries are non-const would
give the behaviour I would have expected), that would probably give a
more intuitive definition of their behaviour.
All comments/explanations/advice on code rewriting gratefully received.
(I'm actually using a non-standard stream class, so changing all the
save/load operators throughout the project to take const streams by
reference is a possibility, tho not one I'm looking forward to.)
Thanks,
Richard.
---
[ comp.std.c++ is moderated. To submit articles: Try just posting with your
newsreader. If that fails, use mailto:std-c++@ncar.ucar.edu
comp.std.c++ FAQ: http://reality.sgi.com/austern/std-c++/faq.html
Moderation policy: http://reality.sgi.com/austern/std-c++/policy.html
Comments? mailto:std-c++-request@ncar.ucar.edu
]
Author: bparker@gil.com.au (Brian Parker)
Date: 1997/06/04 Raw View
On 03 Jun 1997 17:51:52 PDT, Richard See <Richard.See@vega.co.uk>
wrote:
...
>>> The new 32-bit compiler implements a language requirement that may
>>> break some existing code. The new rule is that you cannot pass a
>>> temporary variable by reference--you must pass it by const reference.
...
I agree that whilst disallowing the binding of class temporaries to
non-const references is good default behaviour for most classes, there
are many situations where it is inappropriate, as in your example
(also see my recent post in comp.lang.c++.moderated "Conversions on
initialising references with class temps" for some compelling examples
involving proxy classes with reference semantics.)
A small extension to a suggestion I have made previously would be
useful solution: I think that the draft standard should allow
operatorT&( ) (which returns an lvalue) to be implicitly called when
binding a temporary (i.e. rvalue) to a non-const reference.
At present, Sec 8.5.3 [dcl.init.ref] paragraph 6 unnecessarily
restricts its use to the binding of lvalues (and 12.3.2
[class.conv.fct] paragraph 1 disallows it when returning a reference
of the same type as the class itself).
In your case, this would allow you to add operator ofstream& ( )
{return *this;} to your ofstream class which would convert the temp to
an lvalue, and hence allow its use in calls to functions taking a
non-const reference parameter (however, as always, binding such a
temp returned by reference to a stand-alone reference would be
dangerous and would lead to a dangling reference). Const ofstream
temporaries could be used to disable this binding where it is
inappropriate.
The fundamental issue here is that (non-const) class temporaries,
though officially rvalues, have associated storage and are in fact
modifiable through mutating member functions, and hence in many
situations are used as lvalues. IMO, allowing the implict conversion
to an lvalue via operator T& on initialisation would be a useful way
of indicating such semantics where appropriate for a class.
As a work-around, you could add -
T* operator&() {return this;}
to your ofstream class and use
(*&ofstream("b_file")) << e;
The *& prefix will convert the rvalue to an lvalue.
,Brian Parker (bparker@gil.com.au)
---
[ 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 ]
[ FAQ: http://reality.sgi.com/employees/austern_mti/std-c++/faq.html ]
[ Policy: http://reality.sgi.com/employees/austern_mti/std-c++/policy.html ]
[ Comments? mailto:std-c++-request@ncar.ucar.edu ]