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

end() operation

9 views
Skip to first unread message

Andy

unread,
Jul 11, 2002, 12:22:03 PM7/11/02
to
Hi,

Can anyone help me explain why the two lines of the
following code are not same?

vector<Date> e;
...
e.insert( --e.end(), TodaysDate() ); <-- illegal
e.insert( e.end() -1, TodaysDate() ); <-- legal

Why does --e.end() will modify temporaries of Date*, while
e.end()-1 can execute?

Thanks in advance,

Andy

Igor Tandetnik

unread,
Jul 11, 2002, 12:40:21 PM7/11/02
to
vector::end returns an rvalue, but -- operator needs an
lvalue. --e.end() does not work for the same reason for which

e.end() = e.begin();

does not work.
--
With best wishes,
Igor Tandetnik

"For every complex problem, there is a solution that is simple, neat,
and wrong." H.L. Mencken

"Andy" <andy_w...@yahoo.com> wrote in message
news:17bf101c228f7$17760980$3bef2ecf@TKMSFTNGXA10...

Andy

unread,
Jul 11, 2002, 12:53:45 PM7/11/02
to
Igor,

I guess rvalue means const variable. But the following
code doesn't work as well. Can you explain?

Date* f();
p = --f();

Thanks,

Andy

>.
>

Craig Powers

unread,
Jul 11, 2002, 12:54:40 PM7/11/02
to

Um, because --e.end() attempts to modify e.end() and e.end() - 1 does
not?

--<x> will decrement x and return the result.
<x>-1 will subtract 1 from x (without changing x) and return the result.

Igor Tandetnik

unread,
Jul 11, 2002, 1:01:07 PM7/11/02
to
lvalue is, roughly speaking, something that can occur on the left side
of assignment. rvalue is any expression that is not an lvalue.

To be legal on the left side of an assignment, the expression must refer
to a memory location. Suppose you have (int x) variable. Then (x) refers
to a memory location occupied by x variable and thus you can write (x =
10). But (x + 1) does not refer to any memory location, thus you cannot
write (x + 1 = 10).

A return value of a function is not an lvalue unless it returns a
reference:

int global;
int f1() {return global;}
int* f2() {return &global;}
int& f3() {return global;}


// Illegal. f1 returns a copy of global.
// Said copy does not occupy any particular memory location
f1() = 1;

// Legal. f2 returns a pointer to global. This pointer, when
dereferenced,
// refers to a particular memory location occupied by global.
*f2() = 2;

// Also legal. Very similar to case 2, only the syntax is maybe somewhat
cleaner.
// f3 directly returns a reference to memory occupied by global.
f3() = 3;


Now, the -- operator is supposed to get the value of expression,
decrement it, and put the value back. --exp is essentially the same as
(exp = exp - 1). Thus, an lvalue is required.


--
With best wishes,
Igor Tandetnik

"For every complex problem, there is a solution that is simple, neat,
and wrong." H.L. Mencken

"Andy" <andy_w...@yahoo.com> wrote in message

news:1543701c228fb$84dd1960$36ef2ecf@tkmsftngxa12...

Andy

unread,
Jul 11, 2002, 2:20:54 PM7/11/02
to
Hi Igor,

Thanks you for your excellent explanation!

Can I say that if a function doesn't return a reference,
it will return an anonymous temp variable, which is like a
const variable?

Regards,

Andy

>.
>

Doug Harrison [MVP]

unread,
Jul 11, 2002, 2:25:11 PM7/11/02
to
Andy wrote:

You're using the VC6 vector, whose iterators are plain pointers. The
expression e.end() returns a pointer, which is not modifiable, as it's
a temporary object of a non-class type, so the compiler rejects
--e.end(). In VC7, it would work, because its vector::iterator is a
class type, its operator-- is a member function, and you can call
member functions on temporaries of class type. In general, that's a
good thing, as it's sometimes convenient to say things like X().y().

The error message talks about lvalues, but these are both rvalues. The
whole concept has become rather blurred in C++. On the other hand,
e.end()-1 doesn't modify anything, and the subtraction operator can
use constants or temporaries. It only requires rvalues.

Bonus points to anyone who can explain the following:

#include <vector>

void g(std::vector<int>::iterator&);

void f(std::vector<int>& x)
{
g(x.end()); // Illegal.
g(--x.end()); // Fine in VC7!
}

--
Doug Harrison
Microsoft MVP - Visual C++
Eluent Software, LLC
http://www.eluent.com
Tools for VC++, VS.NET, and Windows

Andy

unread,
Jul 11, 2002, 3:15:45 PM7/11/02
to
So, the function return value is an "anonymance" const
variable, which cannot be referred to?

Hope this time I am right.

Thanks,

Andy

>-----Original Message-----
>There is still a difference. You can take address of a
const variable,
>resulting in a pointer to const. You cannot assign though
this pointer,
>but you can, for example, print the address of the
variable and get a
>real memory location. But you cannot take address of an
rvalue - it does
>not have one.


>--
>With best wishes,
> Igor Tandetnik
>
>"For every complex problem, there is a solution that is
simple, neat,
>and wrong." H.L. Mencken
>
>"Andy" <andy_w...@yahoo.com> wrote in message

>news:1794201c22907$b1a537a0$9be62ecf@tkmsftngxa03...

>Í{ wØ ü
> W ÿVL¼ã ì > >> >> following code are not same?

Igor Tandetnik

unread,
Jul 11, 2002, 3:04:19 PM7/11/02
to
There is still a difference. You can take address of a const variable,
resulting in a pointer to const. You cannot assign though this pointer,
but you can, for example, print the address of the variable and get a
real memory location. But you cannot take address of an rvalue - it does
not have one.
--
With best wishes,
Igor Tandetnik

"For every complex problem, there is a solution that is simple, neat,
and wrong." H.L. Mencken

"Andy" <andy_w...@yahoo.com> wrote in message

news:1794201c22907$b1a537a0$9be62ecf@tkmsftngxa03...

Igor Tandetnik

unread,
Jul 11, 2002, 3:14:26 PM7/11/02
to
I believe Scott Meyers explains the reasons, and it goes something like
this. --x.end() is actually

x.end().operator--();

operator-- returns iterator&. Now, the compiler knows for sure that
x.end() returns a temporary, and temporaries cannot be bound to
non-const references. But the compiler cannot deduce that operator--
still returns a temporary - it could, potentially, return a reference to
some global variable or something. No way to tell at compile time. So
the result loses its temporary status, and can happily bind to a
reference.

It boils down to something like this:

class X
{
public:
X& Detemporize() {return *this;}
};
X tempX() {return X();}
void bindX(X&);

bindX(tempX()); // shouldn't work
bindX(tempX().Detemporize()); // should work

BTW, for some reason both calls compile under VC6 SP5. Is this a bug?


--
With best wishes,
Igor Tandetnik

"For every complex problem, there is a solution that is simple, neat,
and wrong." H.L. Mencken


"Doug Harrison [MVP]" <d...@mvps.org> wrote in message
news:3diriucbbema2dc84...@4ax.com...

Andy

unread,
Jul 11, 2002, 3:19:10 PM7/11/02
to
Hi,

My explanation is the e.end() is not a valid iterator.
Thus, std::vector<int>::iterator& cannot refers to it.

Regards,

Andy

>.
>

Igor Tandetnik

unread,
Jul 11, 2002, 3:18:09 PM7/11/02
to
I guess you can think about them this way. It is just a visualization to help you understand the concept, anyway, so it is good as long as you find it convenient. With best wishes, Igor Tandetnik "For every complex problem, there is a solution that is simple, neat, and wrong." H.L. Mencken "Andy" <andy_w...@yahoo.com> wrote in message news:173f901c2290f$5b485330$b1e62ecf@tkmsftngxa04... So, the function return value is an "anonymance" const variable, which cannot be referred to? Hope this time I am right. Thanks, Andy >-----Original Message----- >There is still a difference. You can take address of a const variable, >resulting in a pointer to const. You cannot assign though this pointer, >but you can, for example, print the address of the variable and get a >real memory location. But you cannot take address of an rvalue - it does >not have one. >With best wishes, > Igor Tandetnik >"For every complex problem, there is a solution that is simple, neat, >and wrong." H.L. Mencken >"Andy" <andy_w...@yahoo.com> wrote in message >news:1794201c22907$b1a537a0$9be62ecf@tkmsftngxa03... >> Hi Igor, >> Thanks you for your excellent explanation! >> Can I say that if a function doesn't return a reference, >> it will return an anonymous temp variable, which is like a >> const variable? >> Regards, >> Andy >> >-----Original Message----- >> >lvalue is, roughly speaking, something that can occur >> the left side >> >of assignment. rvalue is any expression that is not an >> lvalue. >> > >> >To be legal on the left side of an assignment, the >> expression must refer >> >to a memory location. Suppose you have (int x) variable. >> Then (x) refers >> >to a memory location occupied by x variable and thus >> can write (x = >> >10). But (x + 1) does not refer to any memory location, >> thus you cannot >> >write (x + 1 = 10). >> > >> >A return value of a function is not an lvalue unless it >> returns a >> >reference: >> > >> >int global; >> >int f1() {return global;} >> >int* f2() {return &global;} >> >int& f3() {return global;} >> > >> > >> >// Illegal. f1 returns a copy of global. >> >// Said copy does not occupy any particular memory >> location >> >f1() = 1; >> > >> >// Legal. f2 returns a pointer to global. This pointer, >> when >> >dereferenced, >> >// refers to a particular memory location occupied by >> global. >> >*f2() = 2; >> > >> >// Also legal. Very similar to case 2, only the syntax >> maybe somewhat >> >cleaner. >> >// f3 directly returns a reference to memory occupied >> global. >> >f3() = 3; >> > >> > >> >Now, the -- operator is supposed to get the value of >> expression, >> >decrement it, and put the value back. --exp is >> essentially the same as >> >(exp = exp - 1). Thus, an lvalue is required. >> >-- >> >With best wishes, >> > Igor Tandetnik >> > >> >"For every complex problem, there is a solution that is >> simple, neat, >> >and wrong." H.L. Mencken >> > >> >"Andy" <andy_w...@yahoo.com> wrote in message >> >news:1543701c228fb$84dd1960$36ef2ecf@tkmsftngxa12... >> >> Igor, >> >> >> >> I guess rvalue means const variable. But the following >> >> code doesn't work as well. Can you explain? >> >> >> >> Date* f(); >> >> p = --f(); >> >> >> >> Thanks, >> >> >> >> Andy >> >> >-----Original Message----- >> >> >vector::end returns an rvalue, but -- operator needs an >> >> >lvalue. --e.end() does not work for the same reason >> >> which >> >> > >> >> >e.end() = e.begin(); >> >> > >> >> >does not work. >> >> >-- >> >> >With best wishes, >> >> > Igor Tandetnik >> >> > >> >> >"For every complex problem, there is a solution that is >> >> simple, neat, >> >> >and wrong." H.L. Mencken >> >> > >> >> >"Andy" <andy_w...@yahoo.com> wrote in message >> >> >news:17bf101c228f7$17760980$3bef2ecf@TKMSFTNGXA10... >> >> >> Hi, >> >> >> >> >> >> Can anyone help me explain why the two lines of > >> >> following code are not same? >> >> >> >> >> >> vector<Date> e; >> >> >> ... >> >> >> e.insert( --e.end(), TodaysDate() ); <-- illegal >> >> >> e.insert( e.end() -1, TodaysDate() ); <-- legal >> >> >> >> >> >> Why does --e.end() will modify temporaries of Date*, >> >> while

Igor Tandetnik

unread,
Jul 11, 2002, 3:56:26 PM7/11/02
to
Remember, we are talking about whether or not the code compiles. The
iterator may be valid or invalid only at run time. At compile time you
just have an expression of type iterator, without any semantics attached
to it.

--
With best wishes,
Igor Tandetnik

"For every complex problem, there is a solution that is simple, neat,
and wrong." H.L. Mencken

"Andy" <andy_w...@yahoo.com> wrote in message

news:1760d01c2290f$d5563480$9ae62ecf@tkmsftngxa02...

Craig Powers

unread,
Jul 11, 2002, 6:36:07 PM7/11/02
to
Andy wrote:
>
> My explanation is the e.end() is not a valid iterator.
> Thus, std::vector<int>::iterator& cannot refers to it.

As Igor notes, that's a run-time distinction whereas Doug was looking
for a problem that can be detected at compile-time.

No, in this case the reason that's disallowed is a technicality in the
standard. I don't remember the exact reasons why they decided to do
it, but they disallowed binding temporaries to non-const references.
The upshot is, of course, that they thought the benefits in reducing
unexpected behavior outweighed the costs in taking away a possible
construct, but I don't recall exactly what forms of unexpected behavior
they were trying to prevent.

Igor Tandetnik

unread,
Jul 12, 2002, 9:34:09 AM7/12/02
to
Something like this:

class Integer
{
int n;
public:
Integer(int x) : n(x) {}
Integer& operator=(int x) {n = x; return *this};
};

void SetToZero(Integer& value) {value = 0;}

int x = 5;
// A temporary Integer is created and bound to a reference
SetToZero(x);
cout << x; // Surprise: x is still 5

--
With best wishes,
Igor Tandetnik

"For every complex problem, there is a solution that is simple, neat,
and wrong." H.L. Mencken

"Craig Powers" <eni...@hal-pc.org> wrote in message
news:3D2E0857...@hal-pc.org...

Craig Powers

unread,
Jul 12, 2002, 1:08:01 PM7/12/02
to
Igor Tandetnik wrote:
>
> Something like this:
>
> class Integer
> {
> int n;
> public:
> Integer(int x) : n(x) {}
> Integer& operator=(int x) {n = x; return *this};
> };
>
> void SetToZero(Integer& value) {value = 0;}
>
> int x = 5;
> // A temporary Integer is created and bound to a reference
> SetToZero(x);
> cout << x; // Surprise: x is still 5

Yes, I think that's the class of problems that they've said was the
reason for the decision. The hidden creation of temporaries due to
implicit conversions.

Doug Harrison [MVP]

unread,
Jul 13, 2002, 4:30:49 PM7/13/02
to
Igor Tandetnik wrote:

>I believe Scott Meyers explains the reasons, and it goes something like
>this. --x.end() is actually
>
>x.end().operator--();
>
>operator-- returns iterator&. Now, the compiler knows for sure that
>x.end() returns a temporary, and temporaries cannot be bound to
>non-const references. But the compiler cannot deduce that operator--
>still returns a temporary - it could, potentially, return a reference to
>some global variable or something. No way to tell at compile time. So
>the result loses its temporary status, and can happily bind to a
>reference.

That's the best explanation I've heard. I guess one can regard it as
(somewhat) analogous to c_str().

I did find it amusing to discover an example in which making things
look even worse makes it all proper. I mean, if g(x.end()) is illegal,
how can g(--x.end()) be all right? And while x.end()-- is fine(!),
g(x.end()--) is illegal. (This is all VC7.)

>It boils down to something like this:
>
>class X
>{
>public:
> X& Detemporize() {return *this;}
>};
>X tempX() {return X();}
>void bindX(X&);
>
>bindX(tempX()); // shouldn't work
>bindX(tempX().Detemporize()); // should work
>
>BTW, for some reason both calls compile under VC6 SP5. Is this a bug?

By default, VC6 and VC7 allow binding temporaries to non-const
references. You can use -Za to get standard behavior here. Here's a
template which would make conforming to the standard almost painless,
for those times when you really want to bind temporaries to non-const
references, which can be a very useful and convenient capability:

template<typename T>
T&
lvalue_of(const T& x)
{
return const_cast<T&>(x);

tom_usenet

unread,
Jul 13, 2002, 7:03:15 PM7/13/02
to
On Sat, 13 Jul 2002 15:30:49 -0500, "Doug Harrison [MVP]"
<d...@mvps.org> wrote:

>Igor Tandetnik wrote:
>
>>I believe Scott Meyers explains the reasons, and it goes something like
>>this. --x.end() is actually
>>
>>x.end().operator--();
>>
>>operator-- returns iterator&. Now, the compiler knows for sure that
>>x.end() returns a temporary, and temporaries cannot be bound to
>>non-const references. But the compiler cannot deduce that operator--
>>still returns a temporary - it could, potentially, return a reference to
>>some global variable or something. No way to tell at compile time. So
>>the result loses its temporary status, and can happily bind to a
>>reference.
>
>That's the best explanation I've heard. I guess one can regard it as
>(somewhat) analogous to c_str().

Well, it boils down to the fact that you can call member functions on
rvalues, and member functions can return lvalues.

>
>I did find it amusing to discover an example in which making things
>look even worse makes it all proper. I mean, if g(x.end()) is illegal,
>how can g(--x.end()) be all right? And while x.end()-- is fine(!),
>g(x.end()--) is illegal. (This is all VC7.)

This feature can be (ab)used. Occasionally you do want to pass a
temporary to a function as a non-const reference parameter, and you
can do it with by adding a function like ref to your class:

struct s
{
s& ref() { return *this;}
};

void f(s&);

int main()
{
f(s().ref());
}

Tom

Doug Harrison [MVP]

unread,
Jul 14, 2002, 6:02:55 PM7/14/02
to
tom_usenet wrote:

>Well, it boils down to the fact that you can call member functions on
>rvalues, and member functions can return lvalues.

Well, yes. But I'm really looking for a compelling reason I can say:

A().f();

but not:

void g(A& a)
{
a.f();
}

A GetA();

g(A());
g(GetA());

I was thinking that the c_str() analogy is the best rationale for this
I've heard, but I still don't consider it all that compelling. As long
as the compiler doesn't have to create a hidden temporary to bind an
object to a non-const reference, I don't see much danger in binding
explicitly created class temporaries to non-const references.

>>I did find it amusing to discover an example in which making things
>>look even worse makes it all proper. I mean, if g(x.end()) is illegal,
>>how can g(--x.end()) be all right? And while x.end()-- is fine(!),
>>g(x.end()--) is illegal. (This is all VC7.)

And let's not forget:

struct A {};
A() = A();

>This feature can be (ab)used. Occasionally you do want to pass a
>temporary to a function as a non-const reference parameter, and you
>can do it with by adding a function like ref to your class:
>
>struct s
>{
> s& ref() { return *this;}
>};
>
>void f(s&);
>
>int main()
>{
> f(s().ref());
>}

The cast-like function template lvalue_of I presented in my last
message will work with any class and doesn't require the class author
to anticipate that his class might be used in this way.

Come to think about it, I was talking about this in the C++ moderated
groups circa 1997:

>! ... C++ makes some useful idioms involving abstract classes burdensome to
>! implement and explain. For example, suppose A is an abstract class,
>! and you want to pass objects of a derived class X to a function taking
>! an A&. You can't pass by value, and passing by const reference would
>! be misleading, not to mention that it would make writing A and its
>! derived classes more difficult. Now make class X a class template
>! whose instantiations have really long names, that you want to mitigate
>! by a function template that is to class X as std::back_inserter is to
>! std::back_insert_iterator. In order to implement this such that the
>! usage will be as natural as std::back_inserter, and also comply with
>! the DWP, you have to pass by const reference.

This forced me to make all my "sequencer" classes' functions falsely
const, including those functions that advance the sequencer. The input
and output sequencers were a set of classes I wrote to make up for the
lack of member templates in VC5 and to allow writing reasonably
generic functions that could be exported from DLLs. A function such
as:

// class template input_sequencer is based on virtual functions
void visit(const input_sequencer<string>& x);

could be called as:

// c is any container of strings
visit(forward_sequencer(c));

which returns a forward_input_sequencer (derived from input_sequencer)
temporary that implements forward iteration over a container of
strings. (There are also various converting adapters based on
static_cast, hierarchy casting, etc, so it's possible to use a
container of, say, char*, in the example above.) You can't bind a
temporary to a non-const reference, so visit() has to take a const
reference, and to use input_sequencer naturally inside visit(), I had
to make input_sequencer's functions all const. Derived classes of
course had to make their virtual overrides const as well and either
use mutable member variables or cast away const internally.

I think that unless the compiler has to create a hidden temporary
(i.e. a new object) in order to bind an object to a non-const
reference, it ought to allow the binding. I mean, currently I can do
silly things like:

A() = A();
void g(iterator&);
g(--x.end()); // Provided iterators are classes

but I can't implement my sequencer classes in a reasonable way. On the
other hand, this would be possible:

struct X
{
X(A& a) : m_a(a) {}
A& m_a;
};

X x = A();

but I'm already in that boat, having been forced to use const
references for my sequencer parameters. Classes that do the above
document the lifetime issue anyway, and disallowing creation of new
temporaries to complete the binding to a non-const reference would
actually make it less dangerous than using a const reference.

tom_usenet

unread,
Jul 16, 2002, 5:55:38 AM7/16/02
to
On Sun, 14 Jul 2002 17:02:55 -0500, "Doug Harrison [MVP]"
<d...@mvps.org> wrote:

>tom_usenet wrote:
>
>>Well, it boils down to the fact that you can call member functions on
>>rvalues, and member functions can return lvalues.
>
>Well, yes. But I'm really looking for a compelling reason I can say:
>
> A().f();
>
>but not:
>
> void g(A& a)
> {
> a.f();
> }
>
> A GetA();
>
> g(A());
> g(GetA());
>
>I was thinking that the c_str() analogy is the best rationale for this
>I've heard, but I still don't consider it all that compelling. As long
>as the compiler doesn't have to create a hidden temporary to bind an
>object to a non-const reference, I don't see much danger in binding
>explicitly created class temporaries to non-const references.

Agreed. However, deciding the semantics of it wrt overloading is a
little harder.

void g(A&);
void g(A const&);

g(A()); //which is called?

I suppose it could obey the usual overloading rules, but g(A const&)
would only be a candidate function if no user defined conversion is
required. Basically, it could be changed so that any conversion
sequence can only contain 1 user defined conversion, or 1 binding of a
temporary to a non-const reference. This would of course break
existing code, since currently the above would call g(A const&), but
with this change it would call g(A&). Can you think of a way of
specifying it that would cause g(A const&) to be called? I suppose we
could rate the conversion of binding to a non-const reference to be
the worst built in conversion, only above user defined ones and the
ellipsis. That would mean that g(A const&) would be called, and code
wouldn't be broken.

I don't know whether the standards committee considered allowing the
binding in the case that no user defined conversions are required, but
I actually suspect that they didn't want to special case the candidate
function/overloading/reference binding rules since they are complex
enough already.

It also brings in the lifetime extension that binding to a reference
provides.

A const& a = A();

Hopefully the rules for this would remain unchanged (12.2), with the
implicit admission that:

A& a = A();

would work too, since A() can be bound to A&.

Tom

>
>The cast-like function template lvalue_of I presented in my last
>message will work with any class and doesn't require the class author
>to anticipate that his class might be used in this way.

Sorry, missed that before by failing to scroll down!

[snip sequencer example]

>
>I think that unless the compiler has to create a hidden temporary
>(i.e. a new object) in order to bind an object to a non-const
>reference, it ought to allow the binding. I mean, currently I can do
>silly things like:
>
> A() = A();
> void g(iterator&);
> g(--x.end()); // Provided iterators are classes

Well, iterators should generally only ever be passed by value, but I
get your point. The above would be better as:

iterator g(iterator);

Then you could ignore the return or not as you wish.

>
>but I can't implement my sequencer classes in a reasonable way. On the
>other hand, this would be possible:
>
> struct X
> {
> X(A& a) : m_a(a) {}
> A& m_a;
> };
>
> X x = A();
>
>but I'm already in that boat, having been forced to use const
>references for my sequencer parameters. Classes that do the above
>document the lifetime issue anyway, and disallowing creation of new
>temporaries to complete the binding to a non-const reference would
>actually make it less dangerous than using a const reference.

Generally all agreed! However, I just don't think this change will
ever make it into the standard. The wording effort required is
probably greater than the benefit to be gained - persuading someone to
implement the change and checking it doesn't break anything would
probably be quite hard. Howard Hinnant of Metroworks tends to be more
amenable than most to this kind of feature request - if he likes the
idea, he might put in the effort of implementing it.

The const_cast "hack" is a reasonable workaround though, and documents
that you might be doing something dangerous.

Tom

Doug Harrison [MVP]

unread,
Jul 16, 2002, 2:42:44 PM7/16/02
to
tom_usenet wrote:

>Agreed. However, deciding the semantics of it wrt overloading is a
>little harder.
>
>void g(A&);
>void g(A const&);
>
>g(A()); //which is called?
>
>I suppose it could obey the usual overloading rules, but g(A const&)
>would only be a candidate function if no user defined conversion is
>required. Basically, it could be changed so that any conversion
>sequence can only contain 1 user defined conversion, or 1 binding of a
>temporary to a non-const reference. This would of course break
>existing code, since currently the above would call g(A const&), but
>with this change it would call g(A&). Can you think of a way of
>specifying it that would cause g(A const&) to be called? I suppose we
>could rate the conversion of binding to a non-const reference to be
>the worst built in conversion, only above user defined ones and the
>ellipsis. That would mean that g(A const&) would be called, and code
>wouldn't be broken.

Does it even make sense to prefer g(A const&)? I mean, the temporary
created by A() is not const; therefore, g(A&) should be selected.
Whether or not it can successfully bind to the temporary seems like a
separate issue, similar to access checking of class members.

Consider this example:

struct A
{
A& f();
const A& f() const;
void g();
};

A& f(A&);
const A& f(const A&);
void g(A&);

void g()
{
// g(f(A())); // Bad!

A().f().g(); // OK!
}

This is under Comeau. Are the rules really different for member
functions?

>I don't know whether the standards committee considered allowing the
>binding in the case that no user defined conversions are required, but
>I actually suspect that they didn't want to special case the candidate
>function/overloading/reference binding rules since they are complex
>enough already.

To say the least!

>It also brings in the lifetime extension that binding to a reference
>provides.
>
>A const& a = A();
>
>Hopefully the rules for this would remain unchanged (12.2), with the
>implicit admission that:
>
>A& a = A();
>
>would work too, since A() can be bound to A&.

Sure.

>> A() = A();
>> void g(iterator&);
>> g(--x.end()); // Provided iterators are classes
>
>Well, iterators should generally only ever be passed by value, but I
>get your point. The above would be better as:
>
>iterator g(iterator);
>
>Then you could ignore the return or not as you wish.

I'm just using "iterator" because that's how this thread started.

>Generally all agreed! However, I just don't think this change will
>ever make it into the standard. The wording effort required is
>probably greater than the benefit to be gained - persuading someone to
>implement the change and checking it doesn't break anything would
>probably be quite hard. Howard Hinnant of Metroworks tends to be more
>amenable than most to this kind of feature request - if he likes the
>idea, he might put in the effort of implementing it.

You're probably right, though having forgotten about this issue for a
long time, I'm starting to get interested in it again. :)

>The const_cast "hack" is a reasonable workaround though, and documents
>that you might be doing something dangerous.

Yeah, but it's a pain and hardly natural. I really don't want to say
lvalue_of(forward_sequencer(c)) because that feels like
kludge_hack(forward_sequencer(c)). I rejected that approach when I
wrote those classes, and as I described in my last message, having to
make everything const just felt fundamentally wrong.

tom_usenet

unread,
Jul 17, 2002, 6:09:46 AM7/17/02
to
On Tue, 16 Jul 2002 13:42:44 -0500, "Doug Harrison [MVP]"
<d...@mvps.org> wrote:

>tom_usenet wrote:
>
>>Agreed. However, deciding the semantics of it wrt overloading is a
>>little harder.
>>
>>void g(A&);
>>void g(A const&);
>>
>>g(A()); //which is called?
>>
>>I suppose it could obey the usual overloading rules, but g(A const&)
>>would only be a candidate function if no user defined conversion is
>>required. Basically, it could be changed so that any conversion
>>sequence can only contain 1 user defined conversion, or 1 binding of a
>>temporary to a non-const reference. This would of course break
>>existing code, since currently the above would call g(A const&), but
>>with this change it would call g(A&). Can you think of a way of
>>specifying it that would cause g(A const&) to be called? I suppose we
>>could rate the conversion of binding to a non-const reference to be
>>the worst built in conversion, only above user defined ones and the
>>ellipsis. That would mean that g(A const&) would be called, and code
>>wouldn't be broken.
>
>Does it even make sense to prefer g(A const&)? I mean, the temporary
>created by A() is not const; therefore, g(A&) should be selected.

But the point is to avoid breaking existing code. Existing code
chooses g(A const&). Changes that silently change or break existing
code would never be considered for the standard, unless the need for
them was compelling.

>Whether or not it can successfully bind to the temporary seems like a
>separate issue, similar to access checking of class members.

Overloading works by finding the "viable" functions, and then choosing
the best one from this set. Currently, g(A&) isn't viable for g(A()),
so it doesn't make it to the next stage.

>
>Consider this example:
>
>struct A
>{
> A& f();
> const A& f() const;
> void g();
>};
>
>A& f(A&);
>const A& f(const A&);
>void g(A&);
>
>void g()
>{
>// g(f(A())); // Bad!

Right, f(const A&) is chosen, because it is the only viable function.

>
> A().f().g(); // OK!
>}
>
>This is under Comeau. Are the rules really different for member
>functions?

13.3.1/5 describes the difference. The implicit object parameter works
the same as any other parameter except:

no user defined conversions
no extra temporaries can be introduced
binding temporary to non-const ref is allowed (!)

The last point means that A& A::f() is viable as well as the const
version, and it gets chosen since the temporary is non const.

>>Generally all agreed! However, I just don't think this change will
>>ever make it into the standard. The wording effort required is
>>probably greater than the benefit to be gained - persuading someone to
>>implement the change and checking it doesn't break anything would
>>probably be quite hard. Howard Hinnant of Metroworks tends to be more
>>amenable than most to this kind of feature request - if he likes the
>>idea, he might put in the effort of implementing it.
>
>You're probably right, though having forgotten about this issue for a
>long time, I'm starting to get interested in it again. :)
>
>>The const_cast "hack" is a reasonable workaround though, and documents
>>that you might be doing something dangerous.
>
>Yeah, but it's a pain and hardly natural. I really don't want to say
>lvalue_of(forward_sequencer(c)) because that feels like
>kludge_hack(forward_sequencer(c)). I rejected that approach when I
>wrote those classes, and as I described in my last message, having to
>make everything const just felt fundamentally wrong.

I would much rather use the const_cast hack than compromise a class
heirarchy with "mutable" all over the place! const_cast is a perfectly
valid language construct, and solves the problem with minimal impact
on your code.

Tom

Doug Harrison [MVP]

unread,
Jul 17, 2002, 9:30:41 PM7/17/02
to
tom_usenet wrote:

>But the point is to avoid breaking existing code. Existing code
>chooses g(A const&). Changes that silently change or break existing
>code would never be considered for the standard, unless the need for
>them was compelling.

I'm sorry, I'm not thinking about that at all. I have no way of
knowing how much code it would break, and without being able to modify
a compiler, I have no easy way to determine how even my own code would
be affected. My gut feeling (backed up by a cursory search) is that I
don't overload functions based on const vs. non-const reference. In
what real world situation is that useful? You can't use it to simulate
const and non-const member functions using non-members, because the
rules are different, as I demonstrated in my last message. In any
case, I'm interested in determining the "right" way to define
something, and then I'll worry about existing code. I do think it
would be easy for compilers to warn about changes in behavior here.

>13.3.1/5 describes the difference. The implicit object parameter works
>the same as any other parameter except:
>
>no user defined conversions
>no extra temporaries can be introduced
>binding temporary to non-const ref is allowed (!)
>
>The last point means that A& A::f() is viable as well as the const
>version, and it gets chosen since the temporary is non const.

The question is, why are non-member functions treated differently? On
p. 86 of D&E, Stroustrup motivates the "can't bind temporary to
non-const reference rule" with something like:

void incr(int& i) { i++; }

void g()
{
double d = 1;
incr(d); // Converted to int temporary in early C++
}

That's obviously bad, but it's completely different than what I'd like
to do.

>I would much rather use the const_cast hack than compromise a class
>heirarchy with "mutable" all over the place! const_cast is a perfectly
>valid language construct, and solves the problem with minimal impact
>on your code.

My point was that everyone who _uses_ sequencer would have to use
lvalue_of. STL users don't have to say:

lvalue_of(back_inserter(c))

every time they want to use back_inserter, and I wanted to emulate
that interface as closely as possible. Also, people complain they
can't do things like:

ostringstream(...) << blah << blah << blah

This sort of thing comes up fairly often.

0 new messages