Topic: reference to non-const temporary


Author: "Greg Herlihy" <greghe@pacbell.net>
Date: Sun, 30 Jul 2006 00:00:11 CST
Raw View
Ethan Eade wrote:
> johnchx2@yahoo.com wrote:
> > It is arbitrary.  IIRC, the rule was added to the language because
> > programmers were getting unexpected results from code like:
> >
> >   void add_one( long& x ) { x = x + 1; }
> >
> >   int i = 1;
> >   add_one( i );
> >   assert( i == 2 );
> >
> > It's possible to say, "Well, don't make that mistake."  But I gather
> > that, in real life, programmers *did* make this mistake, often enough
> > that the "const rule" was added in order to catch it.
> >
>
> That makes sense.  But surely the restriction can be limited to
> temporaries created by implicit conversion.
>
> As Seungbeom Kim points out above, the rule in its current form merely
> encourages ugly use of methods that return a reference to the object:
>
> struct Foo {
>     Foo& self() { return *this; }
> };
>
> Foo make();
> void use(Foo& foo);
>
> int main() {
>     use(make().self());
> }
>
> That seems silly and pointless.

The example program is somewhat contrived. What does the use() function
do exactly? There should be just one answer to that question. So either
use() should perform some operation for which foo serves as input (in
which case foo should be declared a const reference) or use() should
perform some operation upon its foo parameter (in which case passing a
temporary wouldn't make much sense).

In short there is no defect in the language illustrated by the sample
code, but merely a muddled interface.

Greg

---
[ 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    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: brangdon@cix.co.uk (Dave Harris)
Date: Sun, 30 Jul 2006 15:08:01 CST
Raw View
greghe@pacbell.net (Greg Herlihy) wrote (abridged):
> The example program is somewhat contrived. What does the use() function
> do exactly? There should be just one answer to that question. So either
> use() should perform some operation for which foo serves as input (in
> which case foo should be declared a const reference) or use() should
> perform some operation upon its foo parameter (in which case passing a
> temporary wouldn't make much sense).

By that argument, it should not be possible to ignore function results,
either. There's not much moral difference between:

     void inc_v1( long &x ) { ++x; }

and
     long inc_v2( long x ) { return ++x; }

yet we are allowed to write:

     int i = 0;
     inc_v2( i );

And this is a good thing. Think of strcat(), for example, or printf(), or
any number of other functions whose result is frequently ignored. The
single-purpose purity you desire is an inconvenient burden in practice. If
ignoring results is OK when they are function returns, why not when they
are reference parameters?

In addition, non-const reference arguments are not just used to return
results. Sometimes they are used to provide the function with some
work-space. If the function created (and destroyed) the object itself, it
would incur overhead for each call that could be onerous eg if it is
called in a tight loop. If the caller creates the object outside the loop
and passes it in, the const of construction is only incurred once. In this
case the caller doesn't care what the final value of the object is, and
there's no reason why they shouldn't pass a temporary object.

-- Dave Harris, Nottingham, UK.

---
[ 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    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: brangdon@cix.co.uk (Dave Harris)
Date: Sun, 30 Jul 2006 20:23:48 GMT
Raw View
samee.zahur@gmail.com (Samee Zahur) wrote (abridged):
> >   void add_one( long& x ) { x = x + 1; }
>
> Actually, I've seen this topic come up on this group a number of times,
> and this same rationale being posted a number of times. But this
> problem can only occur when there is an implicit conversion going on.
> Why not just disable implicit conversion whenever initializing a
> non-const ref with a temporary? That way an int temporary could be
> assigned to int& but not long&

Yes, I'm pretty sure I've posted similar comments before. What seems to
have happened is that the gurus have instead pursued the path of providing
a new kind of reference that can bind to non-const temporaries, using the
"&&" syntax. Thus:

     void add_one( long &&x ) { ++x; }

The advantage, as I understand it, is that with both overloads the
programmer /knows/ whether x is bound to a temporary or a variable. He or
she can use this knowledge to make the function more efficient - by not
bothering to fill in the return value, and/or reusing the object passed
for the function's own purposes instead.

The disadvantage, again as I understand it, is that it opens up the
implicit conversion issue again. In effect by using && references you are
saying you are prepared to live with the danger. Could any of the people
supporting the l-value reference proposal confirm whether this issue
remains?

-- Dave Harris, Nottingham, UK.

---
[ 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    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: howard.hinnant@gmail.com (Howard Hinnant)
Date: Sun, 30 Jul 2006 20:41:53 GMT
Raw View
In article <eab335$4ks$1@news.Stanford.EDU>,
 musiphil@bawi.org (Seungbeom Kim) wrote:

> > Logic dictates that you only alter something if there's a reason to.
> >
> > If you pass a temporary, then any such alterations will be lost.
>
> But there are times when we don't care.

Right.

> What useful 'alterations' do you succeed to keep because you can't write

<snip several example uses of rvalue streams>

> ? (The gain is a pain in the neck, I think.)

<nod>

> Is anything going on to change this?

Many people have been contributing to this over the past 5 years:

http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2006/n2027.html

The language change was voted in favor of by the Evolution Working Group
in Apr 2005, and has been under review of the Core Working Group since
Oct 2005.  It has not yet made it into the C++0X working draft:

http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2006/n2009.pdf

If accepted, this would allow:

    void process(std::istream&&);

    process(std::ifstream(filename));

N1690 (referenced from N2027) details more uses involving rvalue
streams, as well as other uses of the rvalue reference.

-Howard

---
[ 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    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: howard.hinnant@gmail.com (Howard Hinnant)
Date: Sun, 30 Jul 2006 21:41:17 GMT
Raw View
In article <memo.20060730194459.2336A@brangdon.cix.compulink.co.uk>,
 brangdon@cix.co.uk (Dave Harris) wrote:

> Yes, I'm pretty sure I've posted similar comments before. What seems to
> have happened is that the gurus have instead pursued the path of providing
> a new kind of reference that can bind to non-const temporaries, using the
> "&&" syntax. Thus:
>
>      void add_one( long &&x ) { ++x; }
>
> The advantage, as I understand it, is that with both overloads the
> programmer /knows/ whether x is bound to a temporary or a variable. He or
> she can use this knowledge to make the function more efficient - by not
> bothering to fill in the return value, and/or reusing the object passed
> for the function's own purposes instead.
>
> The disadvantage, again as I understand it, is that it opens up the
> implicit conversion issue again. In effect by using && references you are
> saying you are prepared to live with the danger. Could any of the people
> supporting the l-value reference proposal confirm whether this issue
> remains?

Yes, the implicit conversion issue remains with &&.  It is up to the
function designer to choose the correct type of reference.  The rvalue
reference (&&) should not be viewed as a "fixed reference".  Both the
lvalue reference (&) and rvalue reference (&&) have valuable use cases.
In the example "add_one" above, use of && is a poor choice since the
intent of this function is to modify the parameter, and that is the only
output.

Take an auto_ptr-like constructor as the counter example:

template <class T>
auto_ptr<T>::auto_ptr(auto_ptr&& p);

Here the "output" of the function is to construct a new auto_ptr.  The
fact that this process modifies the argument is secondary, and not a
logical error if ignored.  Thus here it makes sense to allow binding to
an rvalue (whereas it doesn't make sense if the argument modification is
the only visible effect).

Having both & and && in your tool box allows for a wide range of
powerful techniques, including the ability to disallow rvalue arguments,
or even *only* allow rvalue arguments, if that meets your interface
requirements.

For more info, see:

http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2006/n2027.html

-Howard

---
[ 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    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: jdennett@acm.org (James Dennett)
Date: Mon, 31 Jul 2006 05:23:40 GMT
Raw View
Greg Herlihy wrote:
> Ethan Eade wrote:
>> johnchx2@yahoo.com wrote:
>>> It is arbitrary.  IIRC, the rule was added to the language because
>>> programmers were getting unexpected results from code like:
>>>
>>>   void add_one( long& x ) { x = x + 1; }
>>>
>>>   int i = 1;
>>>   add_one( i );
>>>   assert( i == 2 );
>>>
>>> It's possible to say, "Well, don't make that mistake."  But I gather
>>> that, in real life, programmers *did* make this mistake, often enough
>>> that the "const rule" was added in order to catch it.
>>>
>> That makes sense.  But surely the restriction can be limited to
>> temporaries created by implicit conversion.
>>
>> As Seungbeom Kim points out above, the rule in its current form merely
>> encourages ugly use of methods that return a reference to the object:
>>
>> struct Foo {
>>     Foo& self() { return *this; }
>> };
>>
>> Foo make();
>> void use(Foo& foo);
>>
>> int main() {
>>     use(make().self());
>> }
>>
>> That seems silly and pointless.
>
> The example program is somewhat contrived. What does the use() function
> do exactly? There should be just one answer to that question. So either
> use() should perform some operation for which foo serves as input (in
> which case foo should be declared a const reference) or use() should
> perform some operation upon its foo parameter (in which case passing a
> temporary wouldn't make much sense).
>
> In short there is no defect in the language illustrated by the sample
> code, but merely a muddled interface.

Not necessarily.  A common example of this is using a stream
object; we can't use a const stream (as we can't write to such
a thing), and yet we're often not interested in the state of
the stream itself, but on its side-effects.  In the context
of std::ostream, flush() is often used for self().

-- James

---
[ 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    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: Ethan Eade <ee231@cam.ac.uk>
Date: Thu, 27 Jul 2006 12:26:11 CST
Raw View
Frederick Gotham wrote:
> If you pass either:
>
>     (1) non-const reference
>     (2) pointer to non-const
>
> to a function, then that implies that the function is going to alter
> something.
>
> Logic dictates that you only alter something if there's a reason to.

But C++ does not otherwise disallow you from doing things that have no
logical effect.  I can do this:

{
    auto tmp = make();
    use(tmp);
}

even though tmp is guaranteed to disappear just as in

use(make());

The problem is that I cannot see how to correctly write the "fill"
function shown below so that it can work on 'array's and 'array_slice's
(obviously this is a much simplified version of my problem):

//-------------------------------------
struct array_slice {
     int* x;
     int N;
     array_slice(int* x_, int N_) : x(x_), N(N_) {}

     int size() const { return N; }
     int& operator[](int i) { return x[i]; }
};

template <int N> struct array {
     int x[N];

     int size() const { return N; }
     int& operator[](int i) { return x[i];}

     array_slice slice(int start, int size) {
 return array_slice(x+start, size);
     }
};

template <class A> void fill(A& a) {
     for (int i=0; i<a.size(); ++i)
 a[i] = i;
}

int main()
{
     array<4> a;

     fill(a);  // Works

     fill(a.slice(1,2)); // Fails
}

//-------------------------------------

- Ethan

---
[ 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    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: johnchx2@yahoo.com
Date: Thu, 27 Jul 2006 13:32:45 CST
Raw View
Ethan Eade wrote:

> I'm curious -- why aren't temporaries allowed to be passed as non-const
> references?  It seems slightly arbitrary, since non-const methods can be
> called on them anyway.

It is arbitrary.  IIRC, the rule was added to the language because
programmers were getting unexpected results from code like:

  void add_one( long& x ) { x = x + 1; }

  int i = 1;
  add_one( i );
  assert( i == 2 );

At first glance, it's easy to imagine that add_one( i ) will operate on
i, but, of course, it doesn't -- it operates on a temporary, the result
of the implicit conversion from int to long.

It's possible to say, "Well, don't make that mistake."  But I gather
that, in real life, programmers *did* make this mistake, often enough
that the "const rule" was added in order to catch it.

---
[ 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    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: musiphil@bawi.org (Seungbeom Kim)
Date: Thu, 27 Jul 2006 20:40:54 GMT
Raw View
Frederick Gotham wrote:
> Ethan Eade posted:
>
>> I'm curious -- why aren't temporaries allowed to be passed as non-const
>> references?
>
> If you pass either:
>
>     (1) non-const reference
>     (2) pointer to non-const
>
> to a function, then that implies that the function is going to alter
> something.
>
> Logic dictates that you only alter something if there's a reason to.
>
> If you pass a temporary, then any such alterations will be lost.

But there are times when we don't care.
What useful 'alterations' do you succeed to keep because you can't write

    void process(std::istream&);

    process(std::ifstream(filename));

but instead you have to write

    void process(std::istream&);

    std::ifstream tmp(filename);
    process(tmp);

? Because you can't write

    bool extract(std::istream& is, int& i) { return is >> i; }

    std::string s("123"); int i;
    extract(std::istringstream(s), i);

but instead you have to write

    bool extract(std::istream& is, int& i) { return is >> i; }

    std::string s("123"); int i;
    std::istringstream is(s);
    extract(is, i);
    // "is" not needed any more

? (The gain is a pain in the neck, I think.)
Why does the language offer us "a nice safety feature" that we always
have to accept even though we may not want? Why does it force us to
resort to hacks such as "ostringstream().flush() << ..." instead of
plain, simple, and intuitive "ostringstream() << ..."?

As I have written several times in newsgroups, it does makes sense to
forbid bounding to a non-const reference a temporary created through
implicit conversion (as in

     void incr(int& r) { r++; }
     double d = 1;
     incr(d);

), but not to forbid it for all temporaries, in particular explicitly
created ones such as foo().

Is anything going on to change this?

>> It seems slightly arbitrary, since non-const methods can be
>> called on them anyway.
>
> But it still ensures that we don't discard alterations.

How?

    struct foo { int x; void f() { ++x; } };

    foo().f();

How does the language help you not to discard alterations on the
temporary foo()?

>> Surely such temporaries are fully constructed
>> objects.  What's the rationale?  It seems the restriction is trivially
>> circumvented using operator=():
>>
>> struct Foo {};
>>
>> Foo make() { return Foo(); }
>>
>> void use(Foo& foo) { }
>>
>> int main()
>> {
>>      use(make());          // Fails, as discussed above
>>      use(Foo() = make());  // Compiles
>> }
>
> This I just plain don't understand. In the last line in "main", it appears
> you're assigning to an R-value.
>
> Since when can we assign to an R-value?

A long time ago, though I cannot tell exactly.

> Furthermore, since when can this R-value then be passed by non-const
> reference... ?

The return value of the assignment operator is an lvalue.

--
Seungbeom Kim

---
[ 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    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: ee231@cam.ac.uk (Ethan Eade)
Date: Fri, 28 Jul 2006 05:21:52 GMT
Raw View
johnchx2@yahoo.com wrote:
> It is arbitrary.  IIRC, the rule was added to the language because
> programmers were getting unexpected results from code like:
>
>   void add_one( long& x ) { x = x + 1; }
>
>   int i = 1;
>   add_one( i );
>   assert( i == 2 );
>
> It's possible to say, "Well, don't make that mistake."  But I gather
> that, in real life, programmers *did* make this mistake, often enough
> that the "const rule" was added in order to catch it.
>

That makes sense.  But surely the restriction can be limited to
temporaries created by implicit conversion.

As Seungbeom Kim points out above, the rule in its current form merely
encourages ugly use of methods that return a reference to the object:

struct Foo {
    Foo& self() { return *this; }
};

Foo make();
void use(Foo& foo);

int main() {
    use(make().self());
}

That seems silly and pointless.  Or the following, which I consider even
worse:

struct Foo {};

template <class T> struct Ref {
    T value;
    Ref(T t) : value(t) {}
    operator T& () { return value; }
};
...
use(Ref<Foo>(make()));

- Ethan

---
[ 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    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: "Samee Zahur" <samee.zahur@gmail.com>
Date: Fri, 28 Jul 2006 11:27:38 CST
Raw View
johnchx2@yahoo.com wrote:
> Ethan Eade wrote:
>
> > I'm curious -- why aren't temporaries allowed to be passed as non-const
> > references?  It seems slightly arbitrary, since non-const methods can be
> > called on them anyway.
>
> It is arbitrary.  IIRC, the rule was added to the language because
> programmers were getting unexpected results from code like:
>
>   void add_one( long& x ) { x = x + 1; }
>

Actually, I've seen this topic come up on this group a number of times,
and this same rationale being posted a number of times. But this
problem can only occur when there is an implicit conversion going on.
Why not just disable implicit conversion whenever initializing a
non-const ref with a temporary? That way an int temporary could be
assigned to int& but not long&

This solution seems a lot cleaner ... but since its so obvious and yet
not implemented I guess there has to be a reason. But I am curious to
know what that is ... dont see it posted that often :(


Samee

---
[ 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    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: ee231@cam.ac.uk (Ethan Eade)
Date: Wed, 26 Jul 2006 18:43:39 GMT
Raw View
Consider the following:  The failures apply to g++ 3.3.3 and 3.4, but
not 4.01, which happily compiles when line '***' is not present.  Should
it always work according to the standard?

//-------------------------------------------------
struct Foo {
     void foo() {}
     operator Foo& () { return *this; }  // ***
};

Foo make() { return Foo(); }

void use(Foo& foo) {}

int main()
{
     // This line always works
     make().foo();

     // This line fails when *** is commented out
     // using g++ 3.3.3 and 3.4
     use(make());
}
//--------------------------------------------------

Regards,
Ethan Eade

---
[ 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    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: "Greg Herlihy" <greghe@pacbell.net>
Date: Wed, 26 Jul 2006 17:49:53 CST
Raw View
Ethan Eade wrote:
> Consider the following:  The failures apply to g++ 3.3.3 and 3.4, but
> not 4.01, which happily compiles when line '***' is not present.  Should
> it always work according to the standard?
>
> //-------------------------------------------------
> struct Foo {
>      void foo() {}
>      operator Foo& () { return *this; }  // ***
> };
>
> Foo make() { return Foo(); }
>
> void use(Foo& foo) {}
>
> int main()
> {
>      // This line always works
>      make().foo();
>
>      // This line fails when *** is commented out
>      // using g++ 3.3.3 and 3.4
>      use(make());
> }

The compiler should ignore the operator Foo&() conversion method when
converting a Foo to a Foo&, so whether the method is commented in or
out should make no difference to the compiler. Since a temporary cannot
be passed as a non-const reference parameter, the use() function call
should not compile successfully in both cases. (   12.3.2/1 prohibits a
conversion method from being called to convert an object to a reference
to the object's type.)

Greg


---
[ 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    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: ee231@cam.ac.uk (Ethan Eade)
Date: Thu, 27 Jul 2006 03:55:34 GMT
Raw View
Greg Herlihy wrote:
> The compiler should ignore the operator Foo&() conversion method when
> converting a Foo to a Foo&, so whether the method is commented in or
> out should make no difference to the compiler. Since a temporary cannot
> be passed as a non-const reference parameter, the use() function call
> should not compile successfully in both cases. (=A712.3.2/1 prohibits a
> conversion method from being called to convert an object to a reference
> to the object's type.)

Thanks -- so g++ 4.01 is wrong in this case.

I'm curious -- why aren't temporaries allowed to be passed as non-const=20
references?  It seems slightly arbitrary, since non-const methods can be=20
called on them anyway.  Surely such temporaries are fully constructed=20
objects.  What's the rationale?  It seems the restriction is trivially=20
circumvented using operator=3D():

struct Foo {};

Foo make() { return Foo(); }

void use(Foo& foo) { }

int main()
{
     use(make());          // Fails, as discussed above
     use(Foo() =3D make());  // Compiles
}

Thanks,
Ethan

---
[ 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    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: fgothamNO@SPAM.com (Frederick Gotham)
Date: Thu, 27 Jul 2006 14:56:18 GMT
Raw View
Ethan Eade posted:

> I'm curious -- why aren't temporaries allowed to be passed as non-const
> references?


If you pass either:

    (1) non-const reference
    (2) pointer to non-const

to a function, then that implies that the function is going to alter
something.

Logic dictates that you only alter something if there's a reason to.

If you pass a temporary, then any such alterations will be lost.


> It seems slightly arbitrary, since non-const methods can be
> called on them anyway.


But it still ensures that we don't discard alterations.


> Surely such temporaries are fully constructed
> objects.  What's the rationale?  It seems the restriction is trivially
> circumvented using operator=():
>
> struct Foo {};
>
> Foo make() { return Foo(); }
>
> void use(Foo& foo) { }
>
> int main()
> {
>      use(make());          // Fails, as discussed above
>      use(Foo() = make());  // Compiles
> }


This I just plain don't understand. In the last line in "main", it appears
you're assigning to an R-value.

Since when can we assign to an R-value?

Furthermore, since when can this R-value then be passed by non-const
reference... ?

--

Frederick Gotham

---
[ 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    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]