Isn't function resolution consistent?

D

DeMarcus

Hi,

Consider this example where A could be something similar to a home made
std::eek:stream.

class A
{
public:

template<typename T>
A& operator<<( const T& t )
{
std::cout << "Function Template" << std::endl;
return *this;
}
};

// Just some class.
class B
{
};

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

int main()
{
A a;
B b;

a << b;
}

Now, if I run all this in the same file I get the output "Non-member
function". But if I put class B and its operator<<() in a separate file
I get the output "Function Template".

Shouldn't the operator<< resolution be predictable? I want to run the
non-member function if it exists, and the template function otherwise.
How can I achieve that?


Thanks,
Daniel
 
P

Paul Bibbings

DeMarcus said:
Hi,

Consider this example where A could be something similar to a home
made std::eek:stream.

class A
{
public:

template<typename T>
A& operator<<( const T& t )
{
std::cout << "Function Template" << std::endl;
return *this;
}
};

// Just some class.
class B
{
};

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

int main()
{
A a;
B b;

a << b;
}

Now, if I run all this in the same file I get the output "Non-member
function". But if I put class B and its operator<<() in a separate
file I get the output "Function Template".

Shouldn't the operator<< resolution be predictable? I want to run the
non-member function if it exists, and the template function
otherwise. How can I achieve that?

I think that we would need to have information about just what you mean
by "put class B and its operator<<() in a separate file." Reading the
words as is suggests you are making two files where you had one. What
is this second file? Source file (e.g., .cpp) or header (e.g., .hpp)?
How is it integrated into the program you are building? Linked object
file from source file or included header? Without this information
there are too many possibilities as to how you might have achieved this,
and potentially different outcomes in each (some) case(s).

For instance, if we take your statement literally that you "put class B
and its operator<<() in a separate file," how is your class A definition
made available in that file in order to permit the definition of op<<?

Regards

Paul Bibbings
 
D

DeMarcus

Paul said:
I think that we would need to have information about just what you mean
by "put class B and its operator<<() in a separate file." Reading the
words as is suggests you are making two files where you had one. What
is this second file? Source file (e.g., .cpp) or header (e.g., .hpp)?
How is it integrated into the program you are building? Linked object
file from source file or included header? Without this information
there are too many possibilities as to how you might have achieved this,
and potentially different outcomes in each (some) case(s).

For instance, if we take your statement literally that you "put class B
and its operator<<() in a separate file," how is your class A definition
made available in that file in order to permit the definition of op<<?

Apologies! I will better.
Following setup gives me the different function resolution on gcc 4.4.1.

// main.cpp
#include "A.hpp"
#include "B.hpp"
int main()
{
A a;
B b;
a << b;
}


//A.hpp (with appropriate #ifndef A_HPP_)
class A
{
public:

template<typename T>
A& operator<<( const T& t )
{
std::cout << "Function Template" << std::endl;
return *this;
}
};


// B.hpp (with appropriate #ifndef B_HPP_)
class A;
class B
{
};
A& operator<<( A& a, const B& b );

// B.cpp
#include "B.hpp"
#include "A.hpp"
A& operator<<( A& a, const B& b )
{
std::cout << "Non-member Function" << std::endl;
return a;
}
 
P

Paul Bibbings

DeMarcus said:
Apologies! I will better.
Following setup gives me the different function resolution on gcc 4.4.1.

// main.cpp
#include "A.hpp"
#include "B.hpp"
int main()
{
A a;
B b;
a << b;
}


//A.hpp (with appropriate #ifndef A_HPP_)
class A
{
public:

template<typename T>
A& operator<<( const T& t )
{
std::cout << "Function Template" << std::endl;
return *this;
}
};


// B.hpp (with appropriate #ifndef B_HPP_)
class A;
class B
{
};
A& operator<<( A& a, const B& b );

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

Thanks for clarifying that. The thing is, after adding only the missing
include guards and <iostream> header, I am not getting the same results
as you (see below).

==First Try==

12:53:11 Paul Bibbings@JIJOU
/cygdrive/d/CPPProjects/CLCPP/consistent_resolution $cat AB.cpp
// file: AB.cpp

#include <iostream>

class A
{
public:

template<typename T>
A& operator<<( const T& t )
{
std::cout << "Function Template" << std::endl;
return *this;
}
};

// Just some class.
class B
{
};

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

int main()
{
A a;
B b;

a << b;
}

12:59:07 Paul Bibbings@JIJOU
/cygdrive/d/CPPProjects/CLCPP/consistent_resolution
$i686-pc-cygwin-g++-4.4.1 -o AB AB.cpp

12:59:57 Paul Bibbings@JIJOU
/cygdrive/d/CPPProjects/CLCPP/consistent_resolution $./AB
Non-member Function
^^^^^^^^^^^^^^^^^^^


==Second Try==

13:06:10 Paul Bibbings@JIJOU
/cygdrive/d/CPPProjects/CLCPP/consistent_resolution $cat A.hpp
// file: A.hpp

#ifndef A_HPP_
#define A_HPP_

#include <iostream>

class A
{
public:

template<typename T>
A& operator<<( const T& t )
{
std::cout << "Function Template" << std::endl;
return *this;
}
};

#endif /* A_HPP_ */

13:06:19 Paul Bibbings@JIJOU
/cygdrive/d/CPPProjects/CLCPP/consistent_resolution $cat B.hpp
// file: B.hpp

#ifndef B_HPP_
#define B_HPP_

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

#endif /* B_HPP_ */


13:06:23 Paul Bibbings@JIJOU
/cygdrive/d/CPPProjects/CLCPP/consistent_resolution $cat B.cpp
// file: B.cpp

#include "B.hpp"
#include "A.hpp"

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

13:06:27 Paul Bibbings@JIJOU
/cygdrive/d/CPPProjects/CLCPP/consistent_resolution $cat main.cpp
// file: main.cpp

#include "A.hpp"
#include "B.hpp"
int main()
{
A a;
B b;
a << b;
}



13:06:32 Paul Bibbings@JIJOU
/cygdrive/d/CPPProjects/CLCPP/consistent_resolution
$i686-pc-cygwin-g++-4.4.1 -o AB2 main.cpp B.cpp

13:15:55 Paul Bibbings@JIJOU
/cygdrive/d/CPPProjects/CLCPP/consistent_resolution $./AB2
Non-member Function
^^^^^^^^^^^^^^^^^^^

Regards

Paul Bibbings
 
J

John H.

The thing is ... I am not getting the same results
as you

I also tried this on VS2010 and g++4.3.4 and my results were like
those of Mr. Bibbings: it resolved to the free standing version when
available.
 
D

DeMarcus

Paul said:
Thanks for clarifying that. The thing is, after adding only the missing
include guards and <iostream> header, I am not getting the same results
as you (see below).

==First Try==

12:53:11 Paul Bibbings@JIJOU
/cygdrive/d/CPPProjects/CLCPP/consistent_resolution $cat AB.cpp
// file: AB.cpp

#include <iostream>

class A
{
public:

template<typename T>
A& operator<<( const T& t )
{
std::cout << "Function Template" << std::endl;
return *this;
}
};

// Just some class.
class B
{
};

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

int main()
{
A a;
B b;

a << b;
}

12:59:07 Paul Bibbings@JIJOU
/cygdrive/d/CPPProjects/CLCPP/consistent_resolution
$i686-pc-cygwin-g++-4.4.1 -o AB AB.cpp

12:59:57 Paul Bibbings@JIJOU
/cygdrive/d/CPPProjects/CLCPP/consistent_resolution $./AB
Non-member Function
^^^^^^^^^^^^^^^^^^^


==Second Try==

13:06:10 Paul Bibbings@JIJOU
/cygdrive/d/CPPProjects/CLCPP/consistent_resolution $cat A.hpp
// file: A.hpp

#ifndef A_HPP_
#define A_HPP_

#include <iostream>

class A
{
public:

template<typename T>
A& operator<<( const T& t )
{
std::cout << "Function Template" << std::endl;
return *this;
}
};

#endif /* A_HPP_ */

13:06:19 Paul Bibbings@JIJOU
/cygdrive/d/CPPProjects/CLCPP/consistent_resolution $cat B.hpp
// file: B.hpp

#ifndef B_HPP_
#define B_HPP_

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

#endif /* B_HPP_ */


13:06:23 Paul Bibbings@JIJOU
/cygdrive/d/CPPProjects/CLCPP/consistent_resolution $cat B.cpp
// file: B.cpp

#include "B.hpp"
#include "A.hpp"

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

13:06:27 Paul Bibbings@JIJOU
/cygdrive/d/CPPProjects/CLCPP/consistent_resolution $cat main.cpp
// file: main.cpp

#include "A.hpp"
#include "B.hpp"
int main()
{
A a;
B b;
a << b;
}



13:06:32 Paul Bibbings@JIJOU
/cygdrive/d/CPPProjects/CLCPP/consistent_resolution
$i686-pc-cygwin-g++-4.4.1 -o AB2 main.cpp B.cpp

13:15:55 Paul Bibbings@JIJOU
/cygdrive/d/CPPProjects/CLCPP/consistent_resolution $./AB2
Non-member Function
^^^^^^^^^^^^^^^^^^^

Regards

Paul Bibbings

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?
 
D

DeMarcus

DeMarcus said:
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?

A more illustrating example is actually the following.

// file: main.cpp
#include "A.hpp"
#include "B.hpp"

int main()
{
A a;
B b;

a << b;
A() << b;
}
 
P

Paul Bibbings

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?

Okay. It seems that we now come to the /real/ source of why you were not
getting the results you expected, but this was not part of your original
specification, so the journey here was a little around the issue.

Using all the code we have so far and merely making this change in main,
the first thing to note is that your A() in the expression A() << b is
now an rvalue, whereas before it was an lvalue. This cannot bind to the
first parameter of the non-member function:

A& operator<<(A& a, const B& b);

Here is the problem in isolation:

/cygdrive/d/CPPProjects/CLCPP/consistent_resolution $cat call_with_rvalue.cpp
// file: call_with_rvalue.cpp

class A { };
class B { };

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

int main()
{
B b;
A() << b;
}


09:32:25 Paul Bibbings@JIJOU
/cygdrive/d/CPPProjects/CLCPP/consistent_resolution
$i686-pc-cygwin-gcc-4.4.1 -c call_with_rvalue.cpp
call_with_rvalue.cpp: In function 'int main()':
call_with_rvalue.cpp:11: error: no match for 'operator<<' in 'A() << b'
call_with_rvalue.cpp:6: note: candidates are: A& operator<<(A&, const B&)

So, as you can see, it's not an issue about how the code is structured
after all, but rather with the *extra* change of using an rvalue in the
invocation over an lvalue in the original code. The non-member function
is not, in this instance, ignored because the member function is
suddenly preferred; rather it is that, with this change, the non-member
function is no longer viable.

Related to this, we have here a good illustration of why it is important
to post the *precise* code that causes the problem from the outset.
Investigations have gone round a long route only to find that the cause
originated from some aspect of the code that was only "pulled out of the hat"
at the last minute. We would have reached the solution immediately, had
we had this information from the beginning.

Regards

Paul Bibbings
 
T

tonydee

#include "A.hpp"
#include "B.hpp"

int main()
{
    A a;
    B b;

    a << b;
    A() << b;

}

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...?

Cheers,
Tony
 
D

DeMarcus

Paul said:
Okay. It seems that we now come to the /real/ source of why you were not
getting the results you expected, but this was not part of your original
specification, so the journey here was a little around the issue.

Using all the code we have so far and merely making this change in main,
the first thing to note is that your A() in the expression A() << b is
now an rvalue, whereas before it was an lvalue. This cannot bind to the
first parameter of the non-member function:

A& operator<<(A& a, const B& b);

Here is the problem in isolation:

/cygdrive/d/CPPProjects/CLCPP/consistent_resolution $cat call_with_rvalue.cpp
// file: call_with_rvalue.cpp

class A { };
class B { };

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

int main()
{
B b;
A() << b;
}


09:32:25 Paul Bibbings@JIJOU
/cygdrive/d/CPPProjects/CLCPP/consistent_resolution
$i686-pc-cygwin-gcc-4.4.1 -c call_with_rvalue.cpp
call_with_rvalue.cpp: In function 'int main()':
call_with_rvalue.cpp:11: error: no match for 'operator<<' in 'A() << b'
call_with_rvalue.cpp:6: note: candidates are: A& operator<<(A&, const B&)

So, as you can see, it's not an issue about how the code is structured
after all, but rather with the *extra* change of using an rvalue in the
invocation over an lvalue in the original code. The non-member function
is not, in this instance, ignored because the member function is
suddenly preferred; rather it is that, with this change, the non-member
function is no longer viable.

Related to this, we have here a good illustration of why it is important
to post the *precise* code that causes the problem from the outset.
Investigations have gone round a long route only to find that the cause
originated from some aspect of the code that was only "pulled out of the hat"
at the last minute. We would have reached the solution immediately, had
we had this information from the beginning.

Regards

Paul Bibbings

I try to get to the point as quick as possible, however, it would be
impossible for me (and the reader) to post all source code. Therefore I
tried to pull out the smallest example but realized I missed the
important thing with the rvalue.

Here's a relevant follow-up question. When C++0x comes, shall we change
all the print operators from

std::eek:stream& operator<<( std::eek:stream& s, const SomeClass& sc );

to

std::eek:stream& operator<<( std::eek:stream&& s, const SomeClass& sc );

since the rvalue reference will catch both
a << b;
and
A() << b;

Or will there be other complications doing that change?
 
P

Paul Bibbings

DeMarcus said:
Here's a relevant follow-up question. When C++0x comes, shall we
change all the print operators from

std::eek:stream& operator<<( std::eek:stream& s, const SomeClass& sc );

to

std::eek:stream& operator<<( std::eek:stream&& s, const SomeClass& sc );

Did you perhaps intend this second to be:

std::eek:stream& operator<<(std::eek:stream& s, const SomeClass&& sc ); ?

The first parameter will need to remain std::eek:stream&, or otherwise you
are attempting to create an rvalue-reference temporary to the stream
object which is surely not what you intend.
since the rvalue reference will catch both
a << b;
and
A() << b;

Or will there be other complications doing that change?

I'm a little confused by this, since your usage:

a << b; and A() << b;

refers to your own overloaded op<<'s, which don't match the signature
of the `conventional' op<< that you give the declaration for above. You
appear to be mixing the two in your question.

As to the first (though I appreciate this may not be what you are
asking), there is no need to change the parameter types to rvalue
references, as the following example illustrates:

11:21:27 Paul Bibbings@JIJOU
/cygdrive/d/CPPProjects/CLCPP/consistent_resolution $cat cpp0x_ex.cpp
// file: cpp0x_ex.cpp

#include <iostream>

class SomeClass { };

std::eek:stream& operator<<(std::eek:stream& s, const SomeClass& sc)
{
return s << "op<<";
}

int main()
{
SomeClass sc;

std::cout << sc; // lvalue
std::cout << SomeClass(); // rvalue
std::cout << std::move(sc); // rvalue reference
}


11:21:36 Paul Bibbings@JIJOU
/cygdrive/d/CPPProjects/CLCPP/consistent_resolution
$i686-pc-cygwin-gcc-4.5.0 -std=c++0x -c cpp0x_ex.cpp

11:22:04 Paul Bibbings@JIJOU
/cygdrive/d/CPPProjects/CLCPP/consistent_resolution
$i686-pc-cygwin-g++-4.5.0 -o cpp0x_ex cpp0x_ex.o

11:27:07 Paul Bibbings@JIJOU
/cygdrive/d/CPPProjects/CLCPP/consistent_resolution $./cpp0x_ex
op<<op<<op<<

The key here (in a sense) is the const on the second parameter, which
evidently permits all these combinations.

Regards

Paul Bibbings
 
P

Paul Bibbings

DeMarcus said:
I try to get to the point as quick as possible, however, it would be
impossible for me (and the reader) to post all source code. Therefore
I tried to pull out the smallest example but realized I missed the
important thing with the rvalue.

It can be very hard when you are attempting to pull out the smallest
example from a large section of code in this way. I think that the most
important goals in doing so, however, include the following:

1) The code must compile, or be sufficient to send to a compiler
without additions. (Note: by this I don't mean that it should
compile /successfully/, but where it doesn't it must only fail in
the same way that you have experienced it to fail.)

2) The code must, when compiled and perhaps run, illustrate the
/actual/ problem you are having.

Without intending to be overly critical, your early examples were
incomplete in sense 1) and required additions and assumptions to get
something to send to the compiler and then, when clarified, didn't
illustrate the actual problem you were experiencing in the sense of 2).

It is such considerations that are the challenge of reducing a large
program to a small example that isolates the issue. Sometimes this is a
significantly difficult task, but in my experience is worth doing not
only because it helps people to help you quickly.

If the condensing is done in this way then it helps /you/, the
programmer, focus on where the issue actually originates in your code.
In having to make the example as small as possible, yet still retain the
problem, you are in one sense actually `debugging' your own code. Often
the results of this process are that you actually discover the problem
for yourself, and I would say that in my own experience, whenever I
attempt to produce this kind of minimal, yet complete, example for
posting a question on usenet, only around 1 in 5 actually end up here.
For the others, the solution becomes `obvious' simply through having
gone through the process of localizing the problem in my code and
focussing on as small an example as I can get that still illustrates
it.

Regards

Paul Bibbings
 
D

DeMarcus

Paul said:
Did you perhaps intend this second to be:

std::eek:stream& operator<<(std::eek:stream& s, const SomeClass&& sc ); ?

No, I meant exactly what I wrote this time. ;)

I want to do this:

fnc( const A& a );

int main()
{
B b;
fnc( A() << b );
}

....and still have my non-member function invoked. It works if I do this:

A& operator<<( A&& a, const B& b );
The first parameter will need to remain std::eek:stream&, or otherwise you
are attempting to create an rvalue-reference temporary to the stream
object which is surely not what you intend.

I just intend to call the non-member function for both
A() << b;
and
a << b;

In
A& operator<<( A&& a, const B& b );
will A&& do something with what's input there? I've read somewhere that
rvalue references will be used for move semantics. Would anything input
here be moved?

I'm a little confused by this, since your usage:

a << b; and A() << b;

refers to your own overloaded op<<'s, which don't match the signature
of the `conventional' op<< that you give the declaration for above. You
appear to be mixing the two in your question.

I'm doing my own kind of stream called A. But I want it to be as similar
to std::eek:stream as possible.
As to the first (though I appreciate this may not be what you are
asking), there is no need to change the parameter types to rvalue
references, as the following example illustrates:

11:21:27 Paul Bibbings@JIJOU
/cygdrive/d/CPPProjects/CLCPP/consistent_resolution $cat cpp0x_ex.cpp
// file: cpp0x_ex.cpp

#include <iostream>

class SomeClass { };

std::eek:stream& operator<<(std::eek:stream& s, const SomeClass& sc)
{
return s << "op<<";
}

int main()
{
SomeClass sc;

std::cout << sc; // lvalue
std::cout << SomeClass(); // rvalue
std::cout << std::move(sc); // rvalue reference
}


11:21:36 Paul Bibbings@JIJOU
/cygdrive/d/CPPProjects/CLCPP/consistent_resolution
$i686-pc-cygwin-gcc-4.5.0 -std=c++0x -c cpp0x_ex.cpp

11:22:04 Paul Bibbings@JIJOU
/cygdrive/d/CPPProjects/CLCPP/consistent_resolution
$i686-pc-cygwin-g++-4.5.0 -o cpp0x_ex cpp0x_ex.o

11:27:07 Paul Bibbings@JIJOU
/cygdrive/d/CPPProjects/CLCPP/consistent_resolution $./cpp0x_ex
op<<op<<op<<

The key here (in a sense) is the const on the second parameter, which
evidently permits all these combinations.

Yes, but in my case the ostream may be an rvalue, like this.

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

int main()
{
SomeClass sc;
print( std::stringstream() << sc );
}

therefore I need to define the operator like this

std::eek:stream& operator<<( std::eek:stream&& s, const SomeClass& sc );

or will the rvalue reference do something bad with the stream if I use a
classic output like the following?

std::cout << SomeClass();
 
D

DeMarcus

Paul said:
It can be very hard when you are attempting to pull out the smallest
example from a large section of code in this way. I think that the most
important goals in doing so, however, include the following:

1) The code must compile, or be sufficient to send to a compiler
without additions. (Note: by this I don't mean that it should
compile /successfully/, but where it doesn't it must only fail in
the same way that you have experienced it to fail.)

2) The code must, when compiled and perhaps run, illustrate the
/actual/ problem you are having.

Without intending to be overly critical, your early examples were
incomplete in sense 1) and required additions and assumptions to get
something to send to the compiler and then, when clarified, didn't
illustrate the actual problem you were experiencing in the sense of 2).

It is such considerations that are the challenge of reducing a large
program to a small example that isolates the issue. Sometimes this is a
significantly difficult task, but in my experience is worth doing not
only because it helps people to help you quickly.

If the condensing is done in this way then it helps /you/, the
programmer, focus on where the issue actually originates in your code.
In having to make the example as small as possible, yet still retain the
problem, you are in one sense actually `debugging' your own code. Often
the results of this process are that you actually discover the problem
for yourself, and I would say that in my own experience, whenever I
attempt to produce this kind of minimal, yet complete, example for
posting a question on usenet, only around 1 in 5 actually end up here.
For the others, the solution becomes `obvious' simply through having
gone through the process of localizing the problem in my code and
focussing on as small an example as I can get that still illustrates
it.

Regards

Paul Bibbings

Yes, you are right. I'll post more tested code in the future.
 
P

Paul Bibbings

No, I meant exactly what I wrote this time. ;)

I want to do this:

fnc( const A& a );

int main()
{
B b;
fnc( A() << b );
}

...and still have my non-member function invoked. It works if I do this:

A& operator<<( A&& a, const B& b );

Okay. I'm getting a better sense of this now.

I just intend to call the non-member function for both
A() << b;
and
a << b;

In
A& operator<<( A&& a, const B& b );
will A&& do something with what's input there? I've read somewhere
that rvalue references will be used for move semantics. Would anything
input here be moved?

rvalue references are, of course, suitable for use in implementing move
semantics, but not of themselves. They require support from code that
uses them, such as a move constructor or move assignment operator. If
your code inside the definition of your op<< does not involve any such
things, then actual moving doesn't have to occur. But, see below.

I'm doing my own kind of stream called A. But I want it to be as
similar to std::eek:stream as possible.

So this is your wider purpose. In this case, whilst it seems we have
reached a sense of what will make your original ideas `work', I think I
would want to question those ideas. If we compare your op<< to the
conventional declaration:

std::eek:stream& operator<<(std::eek:stream&, const SomeObject&)

I think that the reference on the first parameter is central to the
design. Implemented in this way there is no copying of the stream
object, and the same is the case into the return value. This permits
chaining of the form:

std::cout << anObject << anotherObject << yetAnotherObject;

into the *same* stream.

If you are going to allow use of your `stream' utilizing temporaries
formed by A(), then you are essentially breaking the `model'. If you
implement it as:

A& operator<<(A&&, const B&)

then you're going to be running into a few problems. Firstly, with this
you are returning a reference to a temporary that has no existence after
the call. So, you would have to adjust and not return a reference, say:

A&& operator<<(A&&, const B&)

But then, your chaining wouldn't work in the same way, and

A() << aB << anotherB << yetAnotherB;

would involve a succession of distinct temporary instances of A.

All in all I think that I would want to steer away from permitting the
use of temporary instances of your stream class in this way, which would
in turn remove the original issue of having to consider adjustments just
to get your free op<< to be called in all valid scenarios.

<snip />

Regards

Paul Bibbings
 
D

DeMarcus

If you are going to allow use of your `stream' utilizing temporaries
formed by A(), then you are essentially breaking the `model'. If you
implement it as:

A& operator<<(A&&, const B&)

then you're going to be running into a few problems. Firstly, with this
you are returning a reference to a temporary that has no existence after
the call.

Yes, but as far as I remember, the temporary should live the whole
full-expression, i.e. the whole chaining. Nevertheless, I feel a bit
uneasy returning a reference to an rvalue reference.

So, you would have to adjust and not return a reference, say:

A&& operator<<(A&&, const B&)

But then, your chaining wouldn't work in the same way,

A first test with

A&& operator<<( A&&, const B& );

showed it seems to work as I expected(!?)
and

A() << aB << anotherB << yetAnotherB;

would involve a succession of distinct temporary instances of A.

But if implement it like this:

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

then it should be the A() temporary only and not distinct ones, right?
(since I return a)


It seems that what I'm trading is the possibility of using the following.

int main()
{
A a; B b;
fnc( a << b );
}

fnc( A& a ); // won't work any more

as well as having a slightly more nasty problem

fnc( A&& a ); // possibly doing some unwanted move on lvalues
 
D

DeMarcus

So, you would have to adjust and not return a reference, say:
A&& operator<<(A&&, const B&)

But then, your chaining wouldn't work in the same way, and

A() << aB << anotherB << yetAnotherB;

would involve a succession of distinct temporary instances of A.

I solved it! I think. How about this in A.hpp?

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

Or for the standard committee... ;)

namespace std
{

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

}
 
P

Paul Bibbings

DeMarcus said:
Yes, but as far as I remember, the temporary should live the whole
full-expression, i.e. the whole chaining.

I would need to look into the detail as I'm a little hazy on whether
that would be the case or not. However, there are other issues here.
One is that chaining is not actually even possible with the above
signature, since the return of an lvalue ref prevents the second
invocation in the chain since the argument is not now an rvalue any
more.

14:33:46 Paul Bibbings@JIJOU
/cygdrive/d/CPPProjects/CLCPP/consistent_resolution $cat
ret_by_ref.cpp
// file: ret_by_ref.cpp

#include <iostream>

class A { };
class B { };

A& operator<<(A&& a, const B&)
{
std::cout << "op<<";
return a;
}

int main()
{
B b;
A() << b; // line 17: OK
A() << b << b; // line 18: error
}

14:33:51 Paul Bibbings@JIJOU
/cygdrive/d/CPPProjects/CLCPP/consistent_resolution
$i686-pc-cygwin-gcc-4.5.0 -std=c++0x -c ret_by_ref.cpp
ret_by_ref.cpp: In function ¡®int main()¡¯:
ret_by_ref.cpp:18:16: error: cannot bind ¡®A¡¯ lvalue to ¡®A&&¡¯
ret_by_ref.cpp:8:4: error: initializing argument 1 of ¡®A&
operator<<(A&&, const B&)¡¯

But if implement it like this:

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

What compiler are you using? I would expect this not to compile since,
within the body of the function, a behaves as an lvalue and hence in the
return should not bind on A&&. The following might work, but then you
have the move semantics that your referred to earlier:

A&& operator<<(A&& a, const B& b) // untested
{
a << "B"; // #1
return std::move(a);
}

<snip />

Regards

Paul Bibbings
 
P

Paul Bibbings

DeMarcus said:
I solved it! I think. How about this in A.hpp?

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

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 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*)

Regards

Paul Bibbings
 
D

DeMarcus

Paul said:
I would need to look into the detail as I'm a little hazy on whether
that would be the case or not. However, there are other issues here.
One is that chaining is not actually even possible with the above
signature, since the return of an lvalue ref prevents the second
invocation in the chain since the argument is not now an rvalue any
more.

14:33:46 Paul Bibbings@JIJOU
/cygdrive/d/CPPProjects/CLCPP/consistent_resolution $cat
ret_by_ref.cpp
// file: ret_by_ref.cpp

#include <iostream>

class A { };
class B { };

A& operator<<(A&& a, const B&)
{
std::cout << "op<<";
return a;
}

int main()
{
B b;
A() << b; // line 17: OK
A() << b << b; // line 18: error
}

14:33:51 Paul Bibbings@JIJOU
/cygdrive/d/CPPProjects/CLCPP/consistent_resolution
$i686-pc-cygwin-gcc-4.5.0 -std=c++0x -c ret_by_ref.cpp
ret_by_ref.cpp: In function ¡®int main()¡¯:
ret_by_ref.cpp:18:16: error: cannot bind ¡®A¡¯ lvalue to ¡®A&&¡¯
ret_by_ref.cpp:8:4: error: initializing argument 1 of ¡®A&
operator<<(A&&, const B&)¡¯



What compiler are you using? I would expect this not to compile since,
within the body of the function, a behaves as an lvalue and hence in the
return should not bind on A&&. The following might work, but then you
have the move semantics that your referred to earlier:

A&& operator<<(A&& a, const B& b) // untested
{
a << "B"; // #1
return std::move(a);
}

It seems to compile fine without std::move(a) in gcc 4.4.1
I went into an endless loop when doing a << "B" so I changed it for
a.set(4711) where set() just set some variable in a. But there were no
problems compiling either of them. I agree it's a bit tricky to
understand fully what to expect from those && variables.
 

Ask a Question

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

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

Ask a Question

Members online

Forum statistics

Threads
473,769
Messages
2,569,579
Members
45,053
Latest member
BrodieSola

Latest Threads

Top