Isn't function resolution consistent?

D

DeMarcus

Paul said:
Clearly you are avoiding infinite recursion here in the call to return
operator<<( a, t ). How are you achieving that? What /other/
definition of op<< are you using? The one defined in the class A? But
that returns A&, doesn't it? And then that shouldn't bind to the return
of A&& here.

I don't know how I achieve that. For that example I stripped everything
so I only have this one left (except for the delegator).

A& operator<<( A& a, const B& b )
{
std::cout << "Non-member Function" << std::endl;
return a;
}


Try this simple example that "worked" for me.

//B.hpp (with appropriate #ifndef B_HPP_)
class B
{
};
std::eek:stream& operator<<( std::eek:stream& s, const B& b );

// B.cpp
std::eek:stream& operator<<( std::eek:stream& s, const B& b )
{
return s << "B";
}

// main.cpp
#include "B.hpp"
#include <sstream>

void fnc( const std::eek:stream& s )
{
std::cout << s.rdbuf() << std::endl;
}

template<typename T>
inline std::eek:stream&& operator<<( std::eek:stream&& s, const T& t )
{
return operator<<( s, t );
}

int main()
{
B b;
fnc( std::stringstream() << b );
}

I might be getting a little lost here, so can I ask... Does your
compiler compile this?:

A&& operator<<(A&& a, const B&)
{
return a;
}

(Note: it *shouldn't*)

If I put the definition in B.cpp it compiles but if I put everything in
B.hpp it doesn't compile with the following error.

multiple definition of 'operator<<(S&&, V const&)'
 
P

Paul Bibbings

I will split my response into two separate posts, for clarity of
issues. Here we'll look at the following.
If I put the definition in B.cpp it compiles but if I put everything
in B.hpp it doesn't compile with the following error.

multiple definition of 'operator<<(S&&, V const&)'

This illustrates that your compiler (gcc-4.4.1) is implementing rvalue
references according to an early specification that has now been
superceded. At present, I don't have all the necessary references to
hand, but there was a significant change in the specification, and one
key part of the change was that, whereas originally it was allowed for
rvalue references to bind to lvalues, now it is not. If gcc-4.4.1 will
compile the above code without requiring:

return std::move(a)

to convert to an rvalue reference, then it is out of date, and
discussion about whether your code (and the model behind it) is valid
is, essentially, not possible on this basis.

To add another point, another change was that, in the body of a function
having a *named* parameter that is an rvalue reference, within that body
it would behave as an lvalue on account of it being named. This is why
std::move(a) is required here, even though we have passed the function
an rvalue reference. Without it, in the bare statement:

return a;

a is an *lvalue* (i.e., no longer an rvalue), and by the first point will
not bind to the return type of A&&.

I would suggest an upgrade, if that is possible. gcc-4.5.0 is available
and handles the changed specification correctly (AFAICT), as does
gcc-4.4.3, I believe.

Regards

Paul Bibbings
 
P

Paul Bibbings

DeMarcus said:
I don't know how I achieve that. For that example I stripped
everything so I only have this one left (except for the delegator).

A& operator<<( A& a, const B& b )
{
std::cout << "Non-member Function" << std::endl;
return a;
}


Try this simple example that "worked" for me.

How can the following work, since you are using std::eek:stream without
including the appropriate header in B.hpp?
//B.hpp (with appropriate #ifndef B_HPP_)
class B
{
};
std::eek:stream& operator<<( std::eek:stream& s, const B& b );

// B.cpp
std::eek:stream& operator<<( std::eek:stream& s, const B& b )
{
return s << "B";
}

How can the following work, since you do not include the appropriate
header to make std::cout, std::endl, etc., available?
// main.cpp
#include "B.hpp"
#include <sstream>

void fnc( const std::eek:stream& s )
{
std::cout << s.rdbuf() << std::endl;
}

template<typename T>
inline std::eek:stream&& operator<<( std::eek:stream&& s, const T& t )
{
return operator<<( s, t );
}

int main()
{
B b;
fnc( std::stringstream() << b );
}

There are other headers missing too. (This is what I was trying to
convey in another post. It is _crucial_ that you post the *exact* code
that you have compiled, or else how can I be sure that my `guesses' at
what is missing don't introduce further problems masking the ones you
are experiencing? If your code needs "appropriate #ifndef" include
guards, put them in!)

However...

As I've mentioned in another post, gcc-4.4.1 is an outdated
implementation with regards to rvalue references and how these are
handled, effectively rendering it worthless (and, indeed, misleading) in
this regard. To illustrate, whilst I can get the code (suitably
corrected) to compile and produce the `expected' results in gcc-4.4.1,
it is nevertheless incorrect according to the specification as it now
stands.

18:54:55 Paul Bibbings@JIJOU
/cygdrive/d/CPPProjects/CLCPP/consistent_resolution/partII
$i686-pc-cygwin-g++-4.5.0 -std=c++0x -o B main.cpp B.cpp
main.cpp: In function ¡®std::eek:stream&& operator<<(std::eek:stream&&,
const T&) [with T = B, std::eek:stream = std::basic_ostream<char>]¡¯:
main.cpp:21:32: instantiated from here
main.cpp:15:28: error: invalid initialization of reference of type
¡®std::eek:stream&&¡¯ from expression of type ¡®std::eek:stream¡¯

In short, experimenting with rvalue references using gcc-4.4.1 is simply
a no-starter, unfortunately. Effectively you are writing code to follow
an implementation that was never ratified as standard, and nor will it
be, rendering it a worthwhile exercise only on historical grounds, and
nothing else.

HTH

Regards

Paul Bibbings
 
J

James Kanze

Paul Bibbings wrote:

[...]
[...]
Yes, but now comes the strange part. Let's change main.cpp slightly to
the following.
// file: main.cpp
#include "A.hpp"
#include "B.hpp"
void fnc( const A& a )
{
}
int main()
{
B b;
fnc( A() << b );
}
Now it prints "Function Template". Why?

Because that's the only function it can legally call. Your free
operator<< takes a non-const reference to A, and you can't
initialize a non-const reference with a temporary.
 
D

DeMarcus

Paul said:
I will split my response into two separate posts, for clarity of
issues. Here we'll look at the following.

This illustrates that your compiler (gcc-4.4.1) is implementing rvalue
references according to an early specification that has now been
superceded. At present, I don't have all the necessary references to
hand, but there was a significant change in the specification, and one
key part of the change was that, whereas originally it was allowed for
rvalue references to bind to lvalues, now it is not. If gcc-4.4.1 will
compile the above code without requiring:

return std::move(a)

to convert to an rvalue reference, then it is out of date, and
discussion about whether your code (and the model behind it) is valid
is, essentially, not possible on this basis.

To add another point, another change was that, in the body of a function
having a *named* parameter that is an rvalue reference, within that body
it would behave as an lvalue on account of it being named. This is why
std::move(a) is required here, even though we have passed the function
an rvalue reference. Without it, in the bare statement:

return a;

a is an *lvalue* (i.e., no longer an rvalue), and by the first point will
not bind to the return type of A&&.

I would suggest an upgrade, if that is possible. gcc-4.5.0 is available
and handles the changed specification correctly (AFAICT), as does
gcc-4.4.3, I believe.

Now, I've upgraded to 4.5.0 and, as you say, get following error:

error: invalid initialization of reference of type 'std::eek:stream&&' from
expression of type 'std::eek:stream'

I'm trying to understand the rvalue reference better. Would my delegator
template work?

template<typename T>
inline std::eek:stream&& operator<<( std::eek:stream&& s, const T& t )
{
return std::move( operator<<( s, t ) );
}

It compiles and runs under gcc 4.5.0, but there are two things that I
can't calculate in my head:

1. Is this a valid treatment of the ostream s? I mean, I'm not allowed
to provide a temporary to a non-const ref, but here it apparently works.
Would this be the proper solution?

template<typename T>
inline std::eek:stream&& operator<<( std::eek:stream&& s, const T& t )
{
std::eek:stream s2( s ); // if future ostreams get move semantics
operator<<( s2, t );
return std::move( s2 );
}


2. Why don't I get an infinite recursion as you suggested in an earlier
post? I tried to clarify for the compiler which function I was looking
for with following.

template<typename T>
inline std::eek:stream&& operator<<( std::eek:stream&& s, const T& t )
{
return std::move( operator<< <S&, const T&>( s, t ) );
}

but got

error: no matching function for call to 'operator<<(std::eek:stream&, const
B&)'

That is strange since that function does exist.

How do I convert a SomeClass&& to a SomeClass& ? It's not possible
unless SomeClass has move semantics, right?
 
P

Paul Bibbings

DeMarcus said:
Now, I've upgraded to 4.5.0 and, as you say, get following error:

error: invalid initialization of reference of type 'std::eek:stream&&'
from expression of type 'std::eek:stream'

I'm trying to understand the rvalue reference better. Would my
delegator template work?

template<typename T>
inline std::eek:stream&& operator<<( std::eek:stream&& s, const T& t )
{
return std::move( operator<<( s, t ) );
}

I'm getting a little lost here. Every now and then you switch
(seemingly randomly) between examples that use what I have supposed is
your custom stream, class A, and then similar examples using
std::eek:stream&(&), as here. For example, in an earlier post you
presented the following as your `delegator'.

Since, at each change, you present only snippets of code, it is very
important, in order to maintain context in my own mind, that I can make
assumptions about what I have seen already. Effectively, I am forced to
ask: "So, which /is/ your delegator?"

I say this because I think it makes a difference to this whole
discussion whether we are talking about some custom stream that you are
building or the std::eek:stream. You seem intent on wanting to make
copious use of temporary instances of your stream class and pass these
around using rvalue references. That is fine, but I certainly wouldn't
want to use this method for std::eek:stream. The reason being that, in
your own type, you are able to support your requirements by adjusting
how you implement your type. std::eek:stream, on the other hand, is not
necessarily - actually, almost certainly not - implemented to fit in
well with your intensions.
It compiles and runs under gcc 4.5.0, but there are two things that I
can't calculate in my head:

1. Is this a valid treatment of the ostream s? I mean, I'm not allowed
to provide a temporary to a non-const ref, but here it apparently
works. Would this be the proper solution?

What is it that leads you to want to pass around temporary objects of
class std::eek:stream in the first place? This is not how it is
conventionally done. Again, I might ask the same question of the
design of your custom stream class. Stream class are surely not cheap
to copy, and perhaps not even that cheap to move.

Overall, I can't help thinking that all your problems, or issues, would
simply disappear if you gave up wanting to use A() << b. Certainly I
have not yet, in this discussion, been able to gain a sense of its
purpose or value.
template<typename T>
inline std::eek:stream&& operator<<( std::eek:stream&& s, const T& t )
{
std::eek:stream s2( s ); // if future ostreams get move semantics
operator<<( s2, t );
return std::move( s2 );
}


2. Why don't I get an infinite recursion as you suggested in an
earlier post? I tried to clarify for the compiler which function I was
looking for with following.

In the body of the op<< function above, the rvalue reference parameter s
behaves as an lvalue. (This was one of the key changes to the current
implementation, IIUC). Thus, it's presence in the return statement

return operator<<( a, t );

will select an overload that accepts an lvalue as its first argument, if
one is available (as, I would suggest, there clearly is).
template<typename T>
inline std::eek:stream&& operator<<( std::eek:stream&& s, const T& t )
{
return std::move( operator<< <S&, const T&>( s, t ) );
} ^
|
What is `S' here ------------------
but got

error: no matching function for call to 'operator<<(std::eek:stream&,
const B&)'

That is strange since that function does exist.

Again, a *complete* compilable example would be necessary here in order
to permit me to comment on this.
How do I convert a SomeClass&& to a SomeClass& ? It's not possible
unless SomeClass has move semantics, right?

Under what circumstances would you want to do this, given the
fundamental difference between rvalue references (as, effectively,
temporary objects) and lvalues (as named regions of addressable
object storage)?

Regards

Paul Bibbings
 
D

DeMarcus

Paul said:
I'm getting a little lost here. Every now and then you switch
(seemingly randomly) between examples that use what I have supposed is
your custom stream, class A, and then similar examples using
std::eek:stream&(&), as here. For example, in an earlier post you
presented the following as your `delegator'.


Since, at each change, you present only snippets of code, it is very
important, in order to maintain context in my own mind, that I can make
assumptions about what I have seen already. Effectively, I am forced to
ask: "So, which /is/ your delegator?"

The final one is this for sure.

template<typename T>
inline A&& operator<<( A&& a, const T& t )
{
std::cout << "Delegating..." << std::endl;
return std::move( operator<<( a, t ) );
}

I will be able to do modifications to the stream-like A.

What is it that leads you to want to pass around temporary objects of
class std::eek:stream in the first place? This is not how it is
conventionally done. Again, I might ask the same question of the
design of your custom stream class. Stream class are surely not cheap
to copy, and perhaps not even that cheap to move.

I definitely want to avoid a copy of A. I can live with one construction
of A though.

If found some code on internet from a guy doing similar things as I.

http://stackoverflow.com/questions/1540831/stringstream-temporary-ostream-return-problem

Overall, I can't help thinking that all your problems, or issues, would
simply disappear if you gave up wanting to use A() << b. Certainly I
have not yet, in this discussion, been able to gain a sense of its
purpose or value.

It provides lazy evaluation of the log text which is important if you
want to switch the language, format and/or destinations in run-time. A
possible scenario could be if you want to send the same log message as a
very short one in a persons native language to her mobile phone, and at
the same time full text in English to the system log.

In the body of the op<< function above, the rvalue reference parameter s
behaves as an lvalue. (This was one of the key changes to the current
implementation, IIUC). Thus, it's presence in the return statement

return operator<<( a, t );

will select an overload that accepts an lvalue as its first argument, if
one is available (as, I would suggest, there clearly is).

Does this mean that my delegator will be valid in C++0x?
|
What is `S' here ------------------

Again, a *complete* compilable example would be necessary here in order
to permit me to comment on this.

I don't know where that S came from. It should be like this.

template<typename T>
inline std::eek:stream&& operator<<( std::eek:stream&& s, const T& t )
{
return std::move( operator<< <std::eek:stream&, const T&>( s, t ) );
}

Isn't it strange that it can't find that operator<< <std::eek:stream&,
const T&> ?

Is the reason that when I use <std::eek:stream&, const T&> it is looking
for a non-member /template/ function, but my function

std::eek:stream& operator<<( std::eek:stream&, const B& );

is not built out of a template?

Under what circumstances would you want to do this, given the
fundamental difference between rvalue references (as, effectively,
temporary objects) and lvalues (as named regions of addressable
object storage)?

As long as the standard says that an rvalue reference behaves as an
lvalue I don't need to convert. I was just thinking for my delegator,
that when it delegates, the next operator<< should not really care where
the memory comes from; if it is static, stack or a temporary shouldn't
matter.

Part of the question is also for my own understanding of how the
creators of rvalue references see this new feature. I don't want to use
rvalue references in the "wrong" way, and then years after coding
someone says; "do never use rvalue references like that!".
 
P

Paul Bibbings

DeMarcus said:
Paul Bibbings wrote:

The final one is this for sure.

template<typename T>
inline A&& operator<<( A&& a, const T& t )
{
std::cout << "Delegating..." << std::endl;
return std::move( operator<<( a, t ) );
}

I will be able to do modifications to the stream-like A.

Okay, that's clear.

Does this mean that my delegator will be valid in C++0x?

Based on what I understand from what you have given, then this will do
the job I feel.

template<typename T>
inline std::eek:stream&& operator<<( std::eek:stream&& s, const T& t )
{
return std::move( operator<< <std::eek:stream&, const T&>( s, t ) );
}

Isn't it strange that it can't find that operator<< <std::eek:stream&,
const T&> ?

Is the reason that when I use <std::eek:stream&, const T&> it is looking
for a non-member /template/ function, but my function

std::eek:stream& operator<<( std::eek:stream&, const B& );

is not built out of a template?

Yes. This is certainly the case. Your op<< overload here is not a
template, but the usage:

operator<< <std::eek:stream&, const T&>( s, t )

is asking the compiler to locate a specific instantiation of a function
*template* that is not present. Having not been able to find it, it
won't find your non-template version in its place since you have
specifically requested a template by providing the template arguments.
As long as the standard says that an rvalue reference behaves as an
lvalue I don't need to convert. I was just thinking for my delegator,
that when it delegates, the next operator<< should not really care
where the memory comes from; if it is static, stack or a temporary
shouldn't matter.

Part of the question is also for my own understanding of how the
creators of rvalue references see this new feature. I don't want to
use rvalue references in the "wrong" way, and then years after coding
someone says; "do never use rvalue references like that!".

To make it just a little more work in getting an accurate
understanding you should be aware that the new Standard expands on the
original pairing of rvalue/lvalue expression categories to:

expression
/ \
/ \
glvalue rvalue
/ \ / \
/ \ / \
lvalue xvalue prvalue

The details of this are in [basic.lval] §3.10. In this context an xvalue
(or eXpiring value) is "an object, usually near the end of its lifetime
(so that its resources may be moved, for example)... [Example: The
result of calling a function whose return type is an rvalue reference is
an xvalue --end example]

Further on, the following is what we have been discussing in relation to
your `delegator':

[expr] §5/6 "[Note: ... In general, the effect of this rule is that
named rvalue references are treated as lvalues and unnamed rvalue
references to objects are treated as xvalues..."

In your delegator, the return value is an unnamed rvalue reference and
as such in an xvalue (essentially, a temporary). On the other hand,
since your parameter A&& a is a *named* rvalue reference in the context
of the method body, then it is "treated as [an] lvalue..."

This is why you are required to use std::move to construct the return
value in some instances, since the purpose of std::move is to convert
(where required) to an rvalue reference. Where the object returned is
"treated as an lvalue" this conversion is required. Where it isn't
required, say in returning an actual (unnamed) rvalue reference, then
std::move is effectively a no-op.

All in all I think you are getting the correct understanding now and
applying it correctly now, in the context of your design. Essentially,
you were not helped at the outset since your compiler was simply not
doing the finally agreed way, but according to an earlier (superceded)
specification.

Regards

Paul Bibbings
 
D

DeMarcus

Paul said:
DeMarcus said:
Paul Bibbings wrote:
The final one is this for sure.

template<typename T>
inline A&& operator<<( A&& a, const T& t )
{
std::cout << "Delegating..." << std::endl;
return std::move( operator<<( a, t ) );
}

I will be able to do modifications to the stream-like A.

Okay, that's clear.

Does this mean that my delegator will be valid in C++0x?

Based on what I understand from what you have given, then this will do
the job I feel.

template<typename T>
inline std::eek:stream&& operator<<( std::eek:stream&& s, const T& t )
{
return std::move( operator<< <std::eek:stream&, const T&>( s, t ) );
}

Isn't it strange that it can't find that operator<< <std::eek:stream&,
const T&> ?

Is the reason that when I use <std::eek:stream&, const T&> it is looking
for a non-member /template/ function, but my function

std::eek:stream& operator<<( std::eek:stream&, const B& );

is not built out of a template?

Yes. This is certainly the case. Your op<< overload here is not a
template, but the usage:

operator<< <std::eek:stream&, const T&>( s, t )

is asking the compiler to locate a specific instantiation of a function
*template* that is not present. Having not been able to find it, it
won't find your non-template version in its place since you have
specifically requested a template by providing the template arguments.
As long as the standard says that an rvalue reference behaves as an
lvalue I don't need to convert. I was just thinking for my delegator,
that when it delegates, the next operator<< should not really care
where the memory comes from; if it is static, stack or a temporary
shouldn't matter.

Part of the question is also for my own understanding of how the
creators of rvalue references see this new feature. I don't want to
use rvalue references in the "wrong" way, and then years after coding
someone says; "do never use rvalue references like that!".

To make it just a little more work in getting an accurate
understanding you should be aware that the new Standard expands on the
original pairing of rvalue/lvalue expression categories to:

expression
/ \
/ \
glvalue rvalue
/ \ / \
/ \ / \
lvalue xvalue prvalue

The details of this are in [basic.lval] §3.10. In this context an xvalue
(or eXpiring value) is "an object, usually near the end of its lifetime
(so that its resources may be moved, for example)... [Example: The
result of calling a function whose return type is an rvalue reference is
an xvalue --end example]

Further on, the following is what we have been discussing in relation to
your `delegator':

[expr] §5/6 "[Note: ... In general, the effect of this rule is that
named rvalue references are treated as lvalues and unnamed rvalue
references to objects are treated as xvalues..."

In your delegator, the return value is an unnamed rvalue reference and
as such in an xvalue (essentially, a temporary). On the other hand,
since your parameter A&& a is a *named* rvalue reference in the context
of the method body, then it is "treated as [an] lvalue..."

This is why you are required to use std::move to construct the return
value in some instances, since the purpose of std::move is to convert
(where required) to an rvalue reference. Where the object returned is
"treated as an lvalue" this conversion is required. Where it isn't
required, say in returning an actual (unnamed) rvalue reference, then
std::move is effectively a no-op.

All in all I think you are getting the correct understanding now and
applying it correctly now, in the context of your design. Essentially,
you were not helped at the outset since your compiler was simply not
doing the finally agreed way, but according to an earlier (superceded)
specification.

Regards

Paul Bibbings

Thank you very much Paul!

Your patience and effort is highly appreciated, and I'm very thankful!

Best Regards,
Daniel
 
D

DeMarcus

tonydee said:
Sorry, I haven't bothered to read through all of this - you're using
an MS compiler I don't have handy anyway - but if this is a "more
illustrating" example of anything, then it can only be that a << b and
A() << b don't produce the same result. Perhaps your issue is that
operator<< takes an A by (non-const) reference, which won't be
selected for the temporary...?

Yes, that was the case. I've learnt a lot on this thread. :)
 

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

No members online now.

Forum statistics

Threads
473,996
Messages
2,570,238
Members
46,826
Latest member
robinsontor

Latest Threads

Top