Bad use of stringstream temporary?

Discussion in 'C++' started by K. Frank, Mar 24, 2011.

  1. K. Frank

    K. Frank Guest

    Hello Group!

    The basic question is whether the following line of code
    is legal and good (according to the current standard):

    std::string s1 =
    dynamic_cast<std::stringstream&>(std::stringstream() << "abc").str();

    The problem is that "abc" gets rendered as a hex pointer
    value, rather than as "abc".

    This is a follow-up on my posting on the MinGW Users List.
    You can find that thread, with more detail and variations
    on the theme, here:

    http://thread.gmane.org/gmane.comp.gnu.mingw.user/35926

    The problem is that we (or at least I) haven't been able
    to identify any specific error in the code, yet three
    different compilers print out the "erroneous" pointer-value
    result (several versions of mingw g++, Comeau 4.3.10.1, and
    msvc 9).

    One speculation is that the code is illegal under the
    current standard, but legal under c++0x. It is the
    case that mingw g++ 4.5 and 4.6 print out the "correct"
    result of "abc" when using the "-std=c++0x" option.
    (mingw g++ 4.4 still prints out the "incorrect" pointer
    value with "-std=c++0x".)

    Here is a short test program and its output:


    << stringstream_test.cpp >>

    #include <iostream>
    #include <sstream>
    #include <typeinfo>
    int main (int argc, char *argv[]) {

    std::string s1 =
    dynamic_cast<std::stringstream&>(std::stringstream() << "abc").str();
    std::cout << "expecting abc: " << s1 << std::endl; // prints out
    "abc" as a pointer

    std::stringstream sstr;
    std::string s2 = dynamic_cast<std::stringstream&>(sstr <<
    "xyz").str();
    std::cout << "expecting xyz: " << s2 << std::endl;

    }


    C:\>g++ -o stringstream_test stringstream_test.cpp

    C:\>stringstream_test
    expecting abc: 0x477024
    expecting xyz: xyz


    That is, the two very similar test cases give different
    results. The version with the unnamed temporary
    stringstream prints out the "incorrect" pointer value,
    while the version with the named local variable works
    as expected.

    (Again, more variations on this sample program that fail
    to compile or give unexpected results can be found in the
    thread on the MinGW Users List linked to above.)

    So, is this code in error (and, if so, what's the specific
    problem)? Or have we stumbled across a cluster of similar
    bugs in a number of different compilers / libraries?

    Thanks for your insight.


    K. Frank
    K. Frank, Mar 24, 2011
    #1
    1. Advertising

  2. On 3/24/2011 12:08 PM, K. Frank wrote:
    > The basic question is whether the following line of code
    > is legal and good (according to the current standard):
    >
    > std::string s1 =
    > dynamic_cast<std::stringstream&>(std::stringstream()<< "abc").str();
    >
    > The problem is that "abc" gets rendered as a hex pointer
    > value, rather than as "abc".
    >
    > This is a follow-up on my posting on the MinGW Users List.
    > You can find that thread, with more detail and variations
    > on the theme, here:
    >
    > http://thread.gmane.org/gmane.comp.gnu.mingw.user/35926
    >
    > The problem is that we (or at least I) haven't been able
    > to identify any specific error in the code, yet three
    > different compilers print out the "erroneous" pointer-value
    > result (several versions of mingw g++, Comeau 4.3.10.1, and
    > msvc 9).
    >
    > One speculation is that the code is illegal under the
    > current standard, but legal under c++0x. It is the
    > case that mingw g++ 4.5 and 4.6 print out the "correct"
    > result of "abc" when using the "-std=c++0x" option.
    > (mingw g++ 4.4 still prints out the "incorrect" pointer
    > value with "-std=c++0x".)
    >
    > Here is a short test program and its output:
    >
    >
    > << stringstream_test.cpp>>
    >
    > #include<iostream>
    > #include<sstream>
    > #include<typeinfo>
    > int main (int argc, char *argv[]) {
    >
    > std::string s1 =
    > dynamic_cast<std::stringstream&>(std::stringstream()<< "abc").str();
    > std::cout<< "expecting abc: "<< s1<< std::endl; // prints out
    > "abc" as a pointer
    >
    > std::stringstream sstr;
    > std::string s2 = dynamic_cast<std::stringstream&>(sstr<<
    > "xyz").str();
    > std::cout<< "expecting xyz: "<< s2<< std::endl;
    >
    > }
    >
    >
    > C:\>g++ -o stringstream_test stringstream_test.cpp
    >
    > C:\>stringstream_test
    > expecting abc: 0x477024
    > expecting xyz: xyz
    >
    >
    > That is, the two very similar test cases give different
    > results. The version with the unnamed temporary
    > stringstream prints out the "incorrect" pointer value,
    > while the version with the named local variable works
    > as expected.
    >
    > (Again, more variations on this sample program that fail
    > to compile or give unexpected results can be found in the
    > thread on the MinGW Users List linked to above.)
    >
    > So, is this code in error (and, if so, what's the specific
    > problem)? Or have we stumbled across a cluster of similar
    > bugs in a number of different compilers / libraries?


    The only function possible is the member function operator<< with the
    argument that is void*. The non-member operator<<(stream&, const char*)
    cannot be used because there is no binding of a temporary to a reference
    to non-const.

    The behavior is as expected.

    V
    --
    I do not respond to top-posted replies, please don't ask
    Victor Bazarov, Mar 24, 2011
    #2
    1. Advertising

  3. K. Frank

    Jeff Flinn Guest

    Victor Bazarov wrote:
    > On 3/24/2011 12:08 PM, K. Frank wrote:
    >> The basic question is whether the following line of code
    >> is legal and good (according to the current standard):
    >>
    >> std::string s1 =
    >> dynamic_cast<std::stringstream&>(std::stringstream()<< "abc").str();
    >>
    >> The problem is that "abc" gets rendered as a hex pointer
    >> value, rather than as "abc".
    >>
    >> This is a follow-up on my posting on the MinGW Users List.
    >> You can find that thread, with more detail and variations
    >> on the theme, here:
    >>
    >> http://thread.gmane.org/gmane.comp.gnu.mingw.user/35926
    >>
    >> The problem is that we (or at least I) haven't been able
    >> to identify any specific error in the code, yet three
    >> different compilers print out the "erroneous" pointer-value
    >> result (several versions of mingw g++, Comeau 4.3.10.1, and
    >> msvc 9).
    >>
    >> One speculation is that the code is illegal under the
    >> current standard, but legal under c++0x. It is the
    >> case that mingw g++ 4.5 and 4.6 print out the "correct"
    >> result of "abc" when using the "-std=c++0x" option.
    >> (mingw g++ 4.4 still prints out the "incorrect" pointer
    >> value with "-std=c++0x".)
    >>
    >> Here is a short test program and its output:
    >>
    >>
    >> << stringstream_test.cpp>>
    >>
    >> #include<iostream>
    >> #include<sstream>
    >> #include<typeinfo>
    >> int main (int argc, char *argv[]) {
    >>
    >> std::string s1 =
    >> dynamic_cast<std::stringstream&>(std::stringstream()<< "abc").str();
    >> std::cout<< "expecting abc: "<< s1<< std::endl; // prints out
    >> "abc" as a pointer
    >>
    >> std::stringstream sstr;
    >> std::string s2 = dynamic_cast<std::stringstream&>(sstr<<
    >> "xyz").str();
    >> std::cout<< "expecting xyz: "<< s2<< std::endl;
    >>
    >> }
    >>
    >>
    >> C:\>g++ -o stringstream_test stringstream_test.cpp
    >>
    >> C:\>stringstream_test
    >> expecting abc: 0x477024
    >> expecting xyz: xyz
    >>
    >>
    >> That is, the two very similar test cases give different
    >> results. The version with the unnamed temporary
    >> stringstream prints out the "incorrect" pointer value,
    >> while the version with the named local variable works
    >> as expected.
    >>
    >> (Again, more variations on this sample program that fail
    >> to compile or give unexpected results can be found in the
    >> thread on the MinGW Users List linked to above.)
    >>
    >> So, is this code in error (and, if so, what's the specific
    >> problem)? Or have we stumbled across a cluster of similar
    >> bugs in a number of different compilers / libraries?

    >
    > The only function possible is the member function operator<< with the
    > argument that is void*. The non-member operator<<(stream&, const char*)
    > cannot be used because there is no binding of a temporary to a reference
    > to non-const.
    >
    > The behavior is as expected.


    IIRC,

    std::string s = (std::eek:stringstream() << std::flush << "abc").string();

    behaves as the OP expected, at least on MSVC8 and gcc4.0.1 under XCode
    3.1.2.

    Jeff
    Jeff Flinn, Mar 24, 2011
    #3
  4. On 3/24/2011 3:13 PM, Jeff Flinn wrote:
    > Victor Bazarov wrote:
    >> On 3/24/2011 12:08 PM, K. Frank wrote:
    >>> The basic question is whether the following line of code
    >>> is legal and good (according to the current standard):
    >>>
    >>> std::string s1 =
    >>> dynamic_cast<std::stringstream&>(std::stringstream()<< "abc").str();
    >>>
    >>> The problem is that "abc" gets rendered as a hex pointer
    >>> value, rather than as "abc".
    >>>
    >>> This is a follow-up on my posting on the MinGW Users List.
    >>> You can find that thread, with more detail and variations
    >>> on the theme, here:
    >>>
    >>> http://thread.gmane.org/gmane.comp.gnu.mingw.user/35926
    >>>
    >>> The problem is that we (or at least I) haven't been able
    >>> to identify any specific error in the code, yet three
    >>> different compilers print out the "erroneous" pointer-value
    >>> result (several versions of mingw g++, Comeau 4.3.10.1, and
    >>> msvc 9).
    >>>
    >>> One speculation is that the code is illegal under the
    >>> current standard, but legal under c++0x. It is the
    >>> case that mingw g++ 4.5 and 4.6 print out the "correct"
    >>> result of "abc" when using the "-std=c++0x" option.
    >>> (mingw g++ 4.4 still prints out the "incorrect" pointer
    >>> value with "-std=c++0x".)
    >>>
    >>> Here is a short test program and its output:
    >>>
    >>>
    >>> << stringstream_test.cpp>>
    >>>
    >>> #include<iostream>
    >>> #include<sstream>
    >>> #include<typeinfo>
    >>> int main (int argc, char *argv[]) {
    >>>
    >>> std::string s1 =
    >>> dynamic_cast<std::stringstream&>(std::stringstream()<< "abc").str();
    >>> std::cout<< "expecting abc: "<< s1<< std::endl; // prints out
    >>> "abc" as a pointer
    >>>
    >>> std::stringstream sstr;
    >>> std::string s2 = dynamic_cast<std::stringstream&>(sstr<<
    >>> "xyz").str();
    >>> std::cout<< "expecting xyz: "<< s2<< std::endl;
    >>>
    >>> }
    >>>
    >>>
    >>> C:\>g++ -o stringstream_test stringstream_test.cpp
    >>>
    >>> C:\>stringstream_test
    >>> expecting abc: 0x477024
    >>> expecting xyz: xyz
    >>>
    >>>
    >>> That is, the two very similar test cases give different
    >>> results. The version with the unnamed temporary
    >>> stringstream prints out the "incorrect" pointer value,
    >>> while the version with the named local variable works
    >>> as expected.
    >>>
    >>> (Again, more variations on this sample program that fail
    >>> to compile or give unexpected results can be found in the
    >>> thread on the MinGW Users List linked to above.)
    >>>
    >>> So, is this code in error (and, if so, what's the specific
    >>> problem)? Or have we stumbled across a cluster of similar
    >>> bugs in a number of different compilers / libraries?

    >>
    >> The only function possible is the member function operator<< with the
    >> argument that is void*. The non-member operator<<(stream&, const
    >> char*) cannot be used because there is no binding of a temporary to a
    >> reference to non-const.
    >>
    >> The behavior is as expected.

    >
    > IIRC,
    >
    > std::string s = (std::eek:stringstream() << std::flush << "abc").string();
    >
    > behaves as the OP expected, at least on MSVC8 and gcc4.0.1 under XCode
    > 3.1.2.


    You meant .str(), not .string(), I am sure, and yes, it does.
    Outputting 'std::flush' (which is a pointer to function) uses a member
    op<<, which returns a ref to a non-const stream which then can be passed
    to a non-member op<< to output the characters.

    V
    --
    I do not respond to top-posted replies, please don't ask
    Victor Bazarov, Mar 24, 2011
    #4
  5. K. Frank

    K. Frank Guest

    Hello Group!

    First off, thanks to all who responded.

    On Mar 24, 3:13 pm, Jeff Flinn <> wrote:
    > Victor Bazarov wrote:
    > > On 3/24/2011 12:08 PM, K. Frank wrote:
    > >> The basic question is whether the following line of code
    > >> is legal and good (according to the current standard):

    >
    > >>     std::string s1 =
    > >> dynamic_cast<std::stringstream&>(std::stringstream()<<  "abc").str();

    >
    > >> The problem is that "abc" gets rendered as a hex pointer
    > >> value, rather than as "abc".

    > ...
    > > The only function possible is the member function operator<< with the
    > > argument that is void*.  The non-member operator<<(stream&, const char*)
    > > cannot be used because there is no binding of a temporary to a reference
    > > to non-const.

    >
    > > The behavior is as expected.


    Yes, this explanation seems to be the consensus. As I understand it,
    the code is perfectly legal, but, because of the temporary
    stringstream,
    operator<< resolves to the member function with argument void*, hence
    printing out the pointer value.

    I believe I understand what everyone is saying, and why this is the
    behavior specified by the standard.

    But there are still some details I don't understand...

    > ...
    > IIRC,
    >
    >   std::string s = (std::eek:stringstream() << std::flush << "abc").string();
    >
    > behaves as the OP expected, at least on MSVC8 and gcc4.0.1 under XCode
    > 3.1.2.


    As Jeff says, adding std::flush causes the code to work as I had
    expected, namely by printing out "abc".

    My variant of the code is:

    std::string s =
    dynamic_cast<std::stringstream&>(std::stringstream() << std::flush <<
    "abc").str();

    Note, inserting other stuff in place of std::flush has the same
    effect:

    std::string s =
    dynamic_cast<std::stringstream&>(std::stringstream() << std::skipws <<
    "abc").str();

    prints out "abc".

    std::string s =
    dynamic_cast<std::stringstream&>(std::stringstream() << 123 <<
    "abc").str();

    prints out "123abc".

    std::string s =
    dynamic_cast<std::stringstream&>(std::stringstream() << "xyz" <<
    "abc").str();

    prints out <pointer value>"abc".

    (I tested these specifically with mingw g++ 4.4.1.)

    I'm not sure exactly how the manipulators are processed, but as I
    understand it, when first inserting 123 or "xyz", the member-function
    operator<< is called, and then returns (*this). (*this) is still
    the unnamed temporary, so the second insertion (<< "abc") should
    again resolve to the member-function operator<< for void*, and so
    should again print out the pointer value rather than "abc".

    It's as if the first insertion operator causes the fact that we're
    processing an unnamed temporary to be forgotten, and the second
    insertion now resolves (incorrectly?) to the free-function operator<<
    for char*, and prints out "abc" rather than the pointer value.

    Is this now a compiler error, or is there another layer of explanation
    that makes this the correct behavior?

    >
    > Jeff


    Thanks Jeff for pointing out the std::flush variation, and thanks to
    all
    for any further insight.


    K. Frank
    K. Frank, Mar 24, 2011
    #5
  6. On 3/24/2011 4:18 PM, K. Frank wrote:
    > Hello Group!
    >
    > First off, thanks to all who responded.
    >
    > On Mar 24, 3:13 pm, Jeff Flinn<> wrote:
    >> Victor Bazarov wrote:
    >>> On 3/24/2011 12:08 PM, K. Frank wrote:
    >>>> The basic question is whether the following line of code
    >>>> is legal and good (according to the current standard):

    >>
    >>>> std::string s1 =
    >>>> dynamic_cast<std::stringstream&>(std::stringstream()<< "abc").str();

    >>
    >>>> The problem is that "abc" gets rendered as a hex pointer
    >>>> value, rather than as "abc".

    >> ...
    >>> The only function possible is the member function operator<< with the
    >>> argument that is void*. The non-member operator<<(stream&, const char*)
    >>> cannot be used because there is no binding of a temporary to a reference
    >>> to non-const.

    >>
    >>> The behavior is as expected.

    >
    > Yes, this explanation seems to be the consensus. As I understand it,
    > the code is perfectly legal, but, because of the temporary
    > stringstream,
    > operator<< resolves to the member function with argument void*, hence
    > printing out the pointer value.
    >
    > I believe I understand what everyone is saying, and why this is the
    > behavior specified by the standard.
    >
    > But there are still some details I don't understand...
    >
    >> ...
    >> IIRC,
    >>
    >> std::string s = (std::eek:stringstream()<< std::flush<< "abc").string();
    >>
    >> behaves as the OP expected, at least on MSVC8 and gcc4.0.1 under XCode
    >> 3.1.2.

    >
    > As Jeff says, adding std::flush causes the code to work as I had
    > expected, namely by printing out "abc".
    >
    > My variant of the code is:
    >
    > std::string s =
    > dynamic_cast<std::stringstream&>(std::stringstream()<< std::flush<<
    > "abc").str();
    >
    > Note, inserting other stuff in place of std::flush has the same
    > effect:
    >
    > std::string s =
    > dynamic_cast<std::stringstream&>(std::stringstream()<< std::skipws<<
    > "abc").str();
    >
    > prints out "abc".
    >
    > std::string s =
    > dynamic_cast<std::stringstream&>(std::stringstream()<< 123<<
    > "abc").str();
    >
    > prints out "123abc".
    >
    > std::string s =
    > dynamic_cast<std::stringstream&>(std::stringstream()<< "xyz"<<
    > "abc").str();
    >
    > prints out<pointer value>"abc".
    >
    > (I tested these specifically with mingw g++ 4.4.1.)
    >
    > I'm not sure exactly how the manipulators are processed, but as I
    > understand it, when first inserting 123 or "xyz", the member-function
    > operator<< is called, and then returns (*this). (*this) is still
    > the unnamed temporary, so the second insertion (<< "abc") should
    > again resolve to the member-function operator<< for void*, and so
    > should again print out the pointer value rather than "abc".


    No. Unnamed temporary it is, but the call to the first member (to
    output the number or the pointer) returns a reference to non-const,
    which then is passed directly to the non-member. There is no binding of
    a reference to a temporary after the first call.

    Example:

    struct A {
    A& foo();
    };

    A& bar(A&);

    int main() {
    A().foo(); // OK
    bar(A().foo()); // OK
    bar(A()); // not OK - attempt to bind a non-const ref to a temp
    }

    > It's as if the first insertion operator causes the fact that we're
    > processing an unnamed temporary to be forgotten, and the second
    > insertion now resolves (incorrectly?) to the free-function operator<<
    > for char*, and prints out "abc" rather than the pointer value.


    No. The temporary is an object. It's temporary, but not constant. The
    language rules require that when a reference is initialized from a
    temporary, the reference has to be to a const object. But since the
    temporary object is non-const, a non-const member function is allowed to
    be called for it. That function can return a reference to non-const,
    and you can initialize another non-const ref with that ref, and so on,
    and use the object (and change it) *as long as* the temporary is still
    *alive*.

    > Is this now a compiler error, or is there another layer of explanation
    > that makes this the correct behavior?


    That's correct behaviour. You can call it a loophole in the language.

    >
    >>
    >> Jeff

    >
    > Thanks Jeff for pointing out the std::flush variation, and thanks to
    > all
    > for any further insight.
    >
    >
    > K. Frank


    V
    --
    I do not respond to top-posted replies, please don't ask
    Victor Bazarov, Mar 24, 2011
    #6
  7. K. Frank

    K. Frank Guest

    Hi Victor!

    And thank you.

    On Mar 24, 4:28 pm, Victor Bazarov <> wrote:
    > On 3/24/2011 4:18 PM, K. Frank wrote:
    > ...
    > >> Victor Bazarov wrote:
    > >>> On 3/24/2011 12:08 PM, K. Frank wrote:
    > >>>> The basic question is whether the following line of code
    > >>>> is legal and good (according to the current standard):

    >
    > >>>> std::string s1 = dynamic_cast<std::stringstream&>(std::stringstream() << "abc").str();

    > ...
    > > Note, inserting other stuff in place of std::flush has the same
    > > effect:

    > ...
    > > std::string s = dynamic_cast<std::stringstream&>(std::stringstream() << 123 << "abc").str();
    > > prints out "123abc".

    > ...
    > > I'm not sure exactly how the manipulators are processed, but as I
    > > understand it, when first inserting 123 or "xyz", the member-function
    > > operator<<  is called, and then returns (*this).  (*this) is still
    > > the unnamed temporary, so the second insertion (<<  "abc") should
    > > again resolve to the member-function operator<<  for void*, and so
    > > should again print out the pointer value rather than "abc".

    >
    > No.  Unnamed temporary it is, but the call to the first member (to
    > output the number or the pointer) returns a reference to non-const,
    > which then is passed directly to the non-member.  There is no binding of
    > a reference to a temporary after the first call.
    >
    > Example:
    >
    >      struct A {
    >         A& foo();
    >      };
    >
    >      A& bar(A&);
    >
    >      int main() {
    >         A().foo(); // OK
    >         bar(A().foo()); // OK
    >         bar(A()); // not OK - attempt to bind a non-const ref to a temp
    >      }
    >
    > > It's as if the first insertion operator causes the fact that we're
    > > processing an unnamed temporary to be forgotten, and the second
    > > insertion now resolves (incorrectly?) to the free-function operator<<
    > > for char*, and prints out "abc" rather than the pointer value.

    >
    > No.  The temporary is an object.  It's temporary, but not constant.  The
    > language rules require that when a reference is initialized from a
    > temporary, the reference has to be to a const object.  But since the
    > temporary object is non-const, a non-const member function is allowed to
    > be called for it.  That function can return a reference to non-const,
    > and you can initialize another non-const ref with that ref, and so on,
    > and use the object (and change it) *as long as* the temporary is still
    > *alive*.
    >
    > > Is this now a compiler error, or is there another layer of explanation
    > > that makes this the correct behavior?

    >
    > That's correct behaviour.  You can call it a loophole in the language.


    Thank you, Victor, for the clear explanation.

    (I will admit that I've now taken a stroll through some alleys and
    byways
    I hadn't intended to tour.)

    I have set myself a homework problem:


    << loophole.cpp >>

    #include <iostream>

    struct Loophole {
    Loophole (int i) : i_(i) {}
    Loophole &loophole() { return *this; }
    int i_;
    };

    void print_incr_i (Loophole &l) {
    l.i_++;
    std::cout << "l.i_ = " << l.i_ << std::endl;
    }

    int main (int argc, char *argv[]) {
    print_incr_i (Loophole (99).loophole());
    // next line gives: error: invalid initialization of non-const
    reference
    print_incr_i (Loophole (66));
    }


    Does this correctly illustrate your explanation? As it stands, the
    program fails to compile, with the error indicated in the comment.
    But after commenting out the second call to print_incr_i, the program
    compiles and prints out 100, as I would expect.

    Even though legal, is this code "bad"? It does seem like I'm
    sneaking around a restriction that the language purposely imposes.

    And if this is bad, is using the temporary stringstream (with the
    "<< std::flush" hack) also bad for the same reason?

    > V


    Thanks again.


    K. Frank
    K. Frank, Mar 24, 2011
    #7
  8. On 3/24/2011 5:50 PM, K. Frank wrote:
    > [..]
    > (I will admit that I've now taken a stroll through some alleys and
    > byways
    > I hadn't intended to tour.)
    >
    > I have set myself a homework problem:
    >
    >
    > << loophole.cpp>>
    >
    > #include<iostream>
    >
    > struct Loophole {
    > Loophole (int i) : i_(i) {}
    > Loophole&loophole() { return *this; }
    > int i_;
    > };
    >
    > void print_incr_i (Loophole&l) {
    > l.i_++;
    > std::cout<< "l.i_ = "<< l.i_<< std::endl;
    > }
    >
    > int main (int argc, char *argv[]) {
    > print_incr_i (Loophole (99).loophole());
    > // next line gives: error: invalid initialization of non-const
    > reference
    > print_incr_i (Loophole (66));
    > }
    >
    >
    > Does this correctly illustrate your explanation? As it stands, the
    > program fails to compile, with the error indicated in the comment.


    Yes, that's the expanded (and functional) version of what my 'foo' and
    'bar' intended to show.

    > But after commenting out the second call to print_incr_i, the program
    > compiles and prints out 100, as I would expect.
    >
    > Even though legal, is this code "bad"? It does seem like I'm
    > sneaking around a restriction that the language purposely imposes.


    I don't think the code is bad. The connotation of a "loophole" is kind
    of negative, but there is nothing seriously negative with this. Of
    course, it is possible to write bad code with it. For instance,

    int main() {
    Loophole& hole = Loophole(99).loophole();
    print_incr_i(hole);
    }

    What's happening here? Simple: undefined behaviour. The object created
    temporarily (the result of the 'Loophole(99)' expression) only lives
    until the semicolon that closes the declaration statement. The 'hole'
    reference is only valid while it's being initialized. Right after
    initialization it becomes invalid, and passing an invalid reference to
    the 'print_incr_i' function (and using it inside) causes undefined
    behaviour. A seasoned programmer will likely avoid this situation.
    However, there can be another, more complicated case, more difficult to
    recognise as dangerous. Consider:

    #include <loophole.h> // Loophole and 'print_incr_i'

    class Doughnut {
    Loophole& myhole;
    public:
    Doughnut(Loophole& hole) : myhole(hole) {}
    void roll() { print_incr_i(myhole); }
    };

    int main() {
    Doughnut(Loophole(99).loophole()).roll(); // all is fine
    }

    In the example above the temporary 'Loophole' survives until 'roll()'
    returns, which is OK. Now, somebody decides to hold onto the doughnut
    and roll it as many times as they need:

    int main() {
    Doughnut creamy(Loophole(99).loophole());
    for (int i = 0; i < 100; ++i)
    creamy.roll();
    }

    As you imagine, it's the same problem as before. The reference held
    inside the Doughnut object becomes invalid as soon as the object is done
    initialising. Using that reference inside 'roll' has undefined behaviour.

    Often, it's even more complex. Doughnuts would be created in the free
    store in one function, where it's unknown the the Loophole is a
    temporary, and used in another function. Consider:

    void bake(Loophole& hole, Doughnut*& pTastyTreat) {
    pTastyTreat = new Doughnut(hole);
    }

    void consume(Doughnut* pRoundThing) {
    if (pRoundThing)
    pRoundThing->roll();
    }

    int main() {
    Loophole good_hole(42);
    Doughnut *pTreat;
    bake(good_hole, pTreat);
    consume(pTreat); // all is good
    delete pTreat;

    bake(Loophole(666).loophole(), pTreat);
    consume(pTreat); // KABOOM!!!
    delete pTreat;
    }

    So, there are generally grades of "bad". If the code can potentially
    create a maintenance problem, it's better avoided.

    > And if this is bad, is using the temporary stringstream (with the
    > "<< std::flush" hack) also bad for the same reason?


    I think it's better than poking yourself in the eye with a sharp stick
    (like one of my old friends liked to say). And it is important to
    remember that every "loophole" has its edges which are better not crossed.

    V
    --
    I do not respond to top-posted replies, please don't ask
    Victor Bazarov, Mar 24, 2011
    #8
  9. K. Frank

    LR Guest

    Victor Bazarov wrote:
    > On 3/24/2011 3:13 PM, Jeff Flinn wrote:
    >> Victor Bazarov wrote:
    >>> On 3/24/2011 12:08 PM, K. Frank wrote:


    >> IIRC,
    >>
    >> std::string s = (std::eek:stringstream() << std::flush << "abc").string();
    >>
    >> behaves as the OP expected, at least on MSVC8 and gcc4.0.1 under XCode
    >> 3.1.2.

    >
    > You meant .str(), not .string(), I am sure, and yes, it does.
    > Outputting 'std::flush' (which is a pointer to function) uses a member
    > op<<, which returns a ref to a non-const stream which then can be passed
    > to a non-member op<< to output the characters.



    Is this certain?


    I've tried
    ----------------------------------------------------------------------
    #include <sstream>
    #include <string>

    int main() {
    const std::string s
    = (std::eek:stringstream() << std::flush << "abc").str();
    }
    ----------------------------------------------------------------------

    Or very similar, with VS2008, GCC 4.5.2 and
    http://www.comeaucomputing.com/tryitout/ see below for version, and all
    three produced error messages similar to,


    -----------------------------------------------------------------------
    Comeau C/C++ 4.3.10.1 (Oct 6 2008 11:28:09) for ONLINE_EVALUATION_BETA2
    Copyright 1988-2008 Comeau Computing. All rights reserved.
    MODE:strict errors C++ C++0x_extensions

    "ComeauTest.c", line 5: error: class
    "std::basic_ostream<char, std::char_traits<char>>" has no member
    "str"
    const std::string s = (std::eek:stringstream() << std::flush << "abc").str();
    -----------------------------------------------------------------------

    LR
    LR, Mar 25, 2011
    #9
  10. On 3/24/2011 8:34 PM, LR wrote:
    > Victor Bazarov wrote:
    >> On 3/24/2011 3:13 PM, Jeff Flinn wrote:
    >>> Victor Bazarov wrote:
    >>>> On 3/24/2011 12:08 PM, K. Frank wrote:

    >
    >>> IIRC,
    >>>
    >>> std::string s = (std::eek:stringstream()<< std::flush<< "abc").string();
    >>>
    >>> behaves as the OP expected, at least on MSVC8 and gcc4.0.1 under XCode
    >>> 3.1.2.

    >>
    >> You meant .str(), not .string(), I am sure, and yes, it does.
    >> Outputting 'std::flush' (which is a pointer to function) uses a member
    >> op<<, which returns a ref to a non-const stream which then can be passed
    >> to a non-member op<< to output the characters.

    >
    >
    > Is this certain?


    Probably not :-/ My memory must not be as good as it used to be...

    >
    >
    > I've tried
    > ----------------------------------------------------------------------
    > #include<sstream>
    > #include<string>
    >
    > int main() {
    > const std::string s
    > = (std::eek:stringstream()<< std::flush<< "abc").str();
    > }
    > ----------------------------------------------------------------------
    >
    > Or very similar, with VS2008, GCC 4.5.2 and
    > http://www.comeaucomputing.com/tryitout/ see below for version, and all
    > three produced error messages similar to,
    >
    >
    > -----------------------------------------------------------------------
    > Comeau C/C++ 4.3.10.1 (Oct 6 2008 11:28:09) for ONLINE_EVALUATION_BETA2
    > Copyright 1988-2008 Comeau Computing. All rights reserved.
    > MODE:strict errors C++ C++0x_extensions
    >
    > "ComeauTest.c", line 5: error: class
    > "std::basic_ostream<char, std::char_traits<char>>" has no member
    > "str"
    > const std::string s = (std::eek:stringstream()<< std::flush<< "abc").str();
    > -----------------------------------------------------------------------
    >
    > LR
    >



    --
    I do not respond to top-posted replies, please don't ask
    Victor Bazarov, Mar 25, 2011
    #10
  11. K. Frank

    Jeff Flinn Guest

    Victor Bazarov wrote:
    > On 3/24/2011 8:34 PM, LR wrote:
    >> Victor Bazarov wrote:
    >>> On 3/24/2011 3:13 PM, Jeff Flinn wrote:
    >>>> Victor Bazarov wrote:
    >>>>> On 3/24/2011 12:08 PM, K. Frank wrote:

    >>
    >>>> IIRC,
    >>>>
    >>>> std::string s = (std::eek:stringstream()<< std::flush<< "abc").string();
    >>>>
    >>>> behaves as the OP expected, at least on MSVC8 and gcc4.0.1 under XCode
    >>>> 3.1.2.
    >>>
    >>> You meant .str(), not .string(), I am sure, and yes, it does.
    >>> Outputting 'std::flush' (which is a pointer to function) uses a member
    >>> op<<, which returns a ref to a non-const stream which then can be passed
    >>> to a non-member op<< to output the characters.

    >>
    >>
    >> Is this certain?

    >
    > Probably not :-/ My memory must not be as good as it used to be...


    Same here, but I'm positive I stumbled on some combination that had
    worked. :-(

    >> I've tried
    >> ----------------------------------------------------------------------
    >> #include<sstream>
    >> #include<string>
    >>
    >> int main() {
    >> const std::string s
    >> = (std::eek:stringstream()<< std::flush<< "abc").str();
    >> }
    >> ----------------------------------------------------------------------
    >>
    >> Or very similar, with VS2008, GCC 4.5.2 and
    >> http://www.comeaucomputing.com/tryitout/ see below for version, and all
    >> three produced error messages similar to,
    >>
    >>
    >> -----------------------------------------------------------------------
    >> Comeau C/C++ 4.3.10.1 (Oct 6 2008 11:28:09) for ONLINE_EVALUATION_BETA2
    >> Copyright 1988-2008 Comeau Computing. All rights reserved.
    >> MODE:strict errors C++ C++0x_extensions
    >>
    >> "ComeauTest.c", line 5: error: class
    >> "std::basic_ostream<char, std::char_traits<char>>" has no
    >> member
    >> "str"
    >> const std::string s = (std::eek:stringstream()<< std::flush<<
    >> "abc").str();
    >> -----------------------------------------------------------------------
    >>
    >> LR
    >>

    >
    >
    Jeff Flinn, Mar 25, 2011
    #11
  12. K. Frank wrote:

    > Even though legal, is this code "bad"? It does seem like I'm sneaking
    > around a restriction that the language purposely imposes.
    >
    > And if this is bad, is using the temporary stringstream (with the "<<
    > std::flush" hack) also bad for the same reason?


    Thanks to you and Victor for this instructive thread. My take is that
    whenever you handle references (or pointers), keeping an eye on the
    lifetime of the referenced (or pointed to) object is important; it
    should not be destroyed before the references (or pointers, or resetting
    the pointers).

    Anything that makes this "keeping an eye on the lifetime" difficult is,
    sort of, "bad". At the very least I think that these "loophole
    techniques" need /very/ good documentation about what they do and how
    they are intended to be used.

    Gerhard
    Gerhard Fiedler, Mar 25, 2011
    #12
  13. K. Frank

    James Kanze Guest

    On Mar 25, 12:34 am, LR <> wrote:
    > Victor Bazarov wrote:
    > > On 3/24/2011 3:13 PM, Jeff Flinn wrote:
    > >> Victor Bazarov wrote:
    > >>> On 3/24/2011 12:08 PM, K. Frank wrote:
    > >> IIRC,


    > >> std::string s = (std::eek:stringstream() << std::flush << "abc").string();


    > >> behaves as the OP expected, at least on MSVC8 and gcc4.0.1 under XCode
    > >> 3.1.2.


    > > You meant .str(), not .string(), I am sure, and yes, it does.
    > > Outputting 'std::flush' (which is a pointer to function) uses a member
    > > op<<, which returns a ref to a non-const stream which then can be passed
    > > to a non-member op<< to output the characters.


    > Is this certain?


    > I've tried
    > ----------------------------------------------------------------------
    > #include <sstream>
    > #include <string>


    > int main() {
    > const std::string s
    > = (std::eek:stringstream() << std::flush << "abc").str();}


    > ----------------------------------------------------------------------


    > Or very similar, with VS2008, GCC 4.5.2 andhttp://www.comeaucomputing.com/tryitout/see below for version, and all
    > three produced error messages similar to,


    > -----------------------------------------------------------------------
    > Comeau C/C++ 4.3.10.1 (Oct 6 2008 11:28:09) for ONLINE_EVALUATION_BETA2
    > Copyright 1988-2008 Comeau Computing. All rights reserved.
    > MODE:strict errors C++ C++0x_extensions
    >
    > "ComeauTest.c", line 5: error: class
    > "std::basic_ostream<char, std::char_traits<char>>" has no member
    > "str"
    > const std::string s = (std::eek:stringstream() << std::flush << "abc").str();
    > -----------------------------------------------------------------------


    The (static) result type of (std::eek:stringstream() << std::flush
    << "abc") is ostream&, which doesn't have an str() function.
    You have to cast this back to ostringstream (the dynamic type)
    to use str() on it, e.g.:

    std::string s =
    static_cast<std::eek:stringstream&>(
    std::eek:stringstream() << std::flush << "abc").str();

    --
    James Kanze
    James Kanze, Mar 26, 2011
    #13
  14. K. Frank

    K. Frank Guest

    Hi Group!

    First off, thanks to Victor and the other respondents for
    sorting this out for me.

    I have a follow-up question concerning this issue and the
    draft c++0x standard.

    On Mar 24, 7:27 pm, Victor Bazarov <> wrote:
    > On 3/24/2011 5:50 PM, K. Frank wrote:
    >
    > > [..]
    > > (I will admit that I've now taken a stroll through some alleys and
    > > byways
    > > I hadn't intended to tour.)

    >
    > > I have set myself a homework problem:

    >
    > > <<  loophole.cpp>>

    > ...
    > > Does this correctly illustrate your explanation?  As it stands, the
    > > program fails to compile, with the error indicated in the comment.

    >
    > Yes, that's the expanded (and functional) version of what my 'foo' and
    > 'bar' intended to show.
    > ...
    > V


    I have a copy of the draft standard, but I don't have a
    copy of the current standard, so I can't make a detailed
    comparison to see what changed.

    However, as I understand it, under the new draft, the issue
    with operator<< is still the same: operator<< for char* is
    still a free function, while operator<< for void* is a
    member function. But it appears that the restriction about
    binding a temporary to a non-const reference may have been
    relaxed.

    As Kai pointed out in the MinGW thread, section 12.2.5 of
    the draft standard talks about binding temporaries to
    references without imposing the restriction that the
    references be const. Our reading suggests that the basic
    example:

    std::string s1 =
    dynamic_cast<std::stringstream&>(std::stringstream() << "abc").str();

    is legal under the draft standard and should print out
    "abc" (rather than the pointer value).

    Now the conundrum.

    (First, I understand that g++ doesn't claim to implement
    completely, or even correctly, the draft standard. But
    at least it's a start.)

    Compiling

    std::string s1 =
    dynamic_cast<std::stringstream&>(std::stringstream() << "abc").str();

    with "g++ -std=c++0x" (version 4.5.2) prints out "abc".
    My interpretation is that the draft standard lets the
    temporary bind to the ostream& reference argument of the
    free function operator<< for char*, so that the call no
    longer resolves to the member function operator<< for void*.

    So far, so good (I think)...

    However, compiling the loophole example:


    << loophole.cpp >>

    #include <iostream>

    struct Loophole {
    Loophole (int i) : i_(i) {}
    Loophole &loophole() { return *this; }
    int i_;

    };

    void print_incr_i (Loophole &l) {
    l.i_++;
    std::cout << "l.i_ = " << l.i_ << std::endl;

    }

    int main (int argc, char *argv[]) {
    print_incr_i (Loophole (99).loophole());
    // next line gives: error: invalid initialization of non-const
    reference
    print_incr_i (Loophole (66));

    }


    with "g++ -std=c++0x" still gives the compile error:

    loophole.cpp: In function 'int main(int, char**)':
    loophole.cpp:17:30: error: invalid initialization of non-const
    reference of type 'Loophole&' from an rvalue of type 'Loophole'
    loophole.cpp:9:6: error: in passing argument 1 of 'void
    print_incr_i(Loophole&)'

    So, the theory was that the draft standard relaxes the
    restriction on binding a temporary to a non-const reference
    (and that "g++ -std=c++0x" implements this change), and
    that's why the stringstream example works. But the
    loophole example contradicts this.

    How should the two examples (stringstream and loophole)
    behave under the draft of the c++0x standard? Is g++
    right in both cases (for reasons that I don't understand),
    or is something half-baked going on here?


    Again, thanks to all for any further insight.


    K. Frank
    K. Frank, Mar 26, 2011
    #14
  15. K. Frank

    SG Guest

    On 26 Mrz., 18:58, K. Frank wrote:
    >
    > I have a follow-up question concerning this issue and the
    > draft c++0x standard.
    > [...]


    C++0x does not allow you to bind temporaries to non-const lvalue
    references. But its standard library provides "rvalue stream
    insertion" (§27.7.2.9):

    template <class charT, class traits, class T>
    basic_ostream<charT, traits>&
    operator<<(basic_ostream<charT, traits>&& os, const T& x);

    Effect : os << x
    Returns: os

    Note the double ampersand (&&). This is an rvalue reference.
    Basically, it only binds to rvalues and you can use it during
    overloading to distinguish between lvalues and rvalues. So, your
    temporary is bound to this rvalue reference called 'os'. 'os' will be
    an lvalue expression referring to your stream object (since it is a
    named reference), 'os << x' will call the free operator<< that takes
    an lvalue stream reference and a pointer of type const char*.

    SG
    SG, Mar 26, 2011
    #15
  16. K. Frank

    K. Frank Guest

    Hi SG!

    Thank you; that clears things up nicely.

    On Mar 26, 2:38 pm, SG <> wrote:
    > On 26 Mrz., 18:58, K. Frank wrote:
    >
    > > I have a follow-up question concerning this issue and the
    > > draft c++0x standard.
    > > [...]

    >
    > C++0x does not allow you to bind temporaries to non-const lvalue
    > references. But its standard library provides "rvalue stream
    > insertion" (§27.7.2.9):
    >
    >   template <class charT, class traits, class T>
    >     basic_ostream<charT, traits>&
    >     operator<<(basic_ostream<charT, traits>&& os, const T& x);
    >
    >   Effect : os << x
    >   Returns: os
    >
    > Note the double ampersand (&&). This is an rvalue reference.
    > Basically, it only binds to rvalues and you can use it during
    > overloading to distinguish between lvalues and rvalues. So, your
    > temporary is bound to this rvalue reference called 'os'. 'os' will be
    > an lvalue expression referring to your stream object (since it is a
    > named reference), 'os << x' will call the free operator<< that takes
    > an lvalue stream reference and a pointer of type const char*.


    Very good. This all makes sense now. The meaning / purpose of the
    new ostream.rvalue section hadn't really registered with me.

    By the way, from what I can tell the purpose of ostream.rvalue is
    to support the use of temporary ostreams in the kind of on-the-fly
    construction I was playing around with that led off this discussion.
    See, for example, the section titled "1203. More useful rvalue stream
    insertion" of:

    http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2009/n2948.html


    To check my understanding, I modified my loophole example to include
    an overloaded version of print_incr_i that takes an rvalue reference
    argument:

    void print_incr_i (Loophole &&l) {
    l.i_ += 2;
    std::cout << "l.i_ = " << l.i_ << std::endl;
    }

    (Note, this version of the function increments i_ by two, so that
    I can get verification of which overloaded function is called in
    which situation.)

    This modified loophole example now compiles (with -std=c++0x), and
    runs as expected in that the rvalue-reference version of the
    overloaded
    function is called for:

    print_incr_i (Loophole (66));

    >
    > SG


    Thanks again for your explanation.


    K. Frank
    K. Frank, Mar 26, 2011
    #16
  17. K. Frank

    SG Guest

    On 27 Mrz., 01:06, K. Frank wrote:
    > Thanks again for your explanation.


    I should probably mention that there is more to && than its ability to
    bind to temporaries. Not knowing more about && might lead to bad
    surprizes. So, here are some more things you ought to know about
    rvalue references:

    For some object type T the type T&& will be an rvalue reference. It's
    like an lvalue reference with two differences: the rules on how such a
    reference can be initialized are different (obviously) and a function
    call with an rvalue reference return type is considered an rvalue
    expression (unlike the name of an rvalue reference).

    For some reference type T the type T&& is not necessarily an rvalue
    reference anymore. If T is an lvalue reference, then so is T&&. This
    is called "reference collapsing". Examples:

    T T&& T&
    ------------------
    int int&& int&
    int&& int&& int&
    int& int& int&

    Finally, there is a funny deduction rule that only applies to the
    pattern T&& where T is a template parameter like in case #2:

    template<class T> void bar(T const&) {} // #1

    template<class T> void bar(T && r) { // #2
    r = 0; // modifying temporaries does not hurt nobody, right?
    }

    int main() {
    int i = 1729;
    bar(99); // #2 with T=int, T&&=int&&
    bar(i); // #2 with T=int&, T&&=int&
    assert(i==1729); // test will FAIL!
    }

    In both calls #2 is picked because it is a better match:

    call #1 #2
    -----------------------------------------------
    bar(99) bar<int>(int const&) bar<int>(int&&)
    bar(i) bar<int>(int const&) bar<int&>(int&)

    This is what I meant by bad surprizes. The purpose of this deduction
    rule is to retain the information about the function argument's value
    category as part of the type parameter. The "perfect forwarding"
    technique relies on this behaviour.

    SG
    SG, Mar 27, 2011
    #17
  18. K. Frank

    K. Frank Guest

    Hello SG!

    Thank you for the further discussion.

    On Mar 27, 8:57 am, SG <> wrote:
    > On 27 Mrz., 01:06, K. Frank wrote:
    >
    > > Thanks again for your explanation.

    >
    > I should probably mention that there is more to && than its ability to
    > bind to temporaries. Not knowing more about && might lead to bad
    > surprizes. So, here are some more things you ought to know about
    > rvalue references:
    > ...


    This is all very helpful. I am trying -- step by step, and over
    time -- to understand rvalue references and move semantics as I
    work to get on board with the new standard.

    I appreciate your explaining some of the details of rvalue references
    and the consequences they can have.


    Best regards.


    K. Frank
    K. Frank, Mar 27, 2011
    #18
  19. On Mar 27, 12:39 pm, "K. Frank" <> wrote:
    > Hello SG!
    >
    > Thank you for the further discussion.
    >
    > On Mar 27, 8:57 am, SG <> wrote:
    >
    > > On 27 Mrz., 01:06, K. Frank wrote:

    >
    > > > Thanks again for your explanation.

    >
    > > I should probably mention that there is more to && than its ability to
    > > bind to temporaries. Not knowing more about && might lead to bad
    > > surprizes. So, here are some more things you ought to know about
    > > rvalue references:
    > > ...

    >
    > This is all very helpful.  I am trying -- step by step, and over
    > time -- to understand rvalue references and move semantics as I
    > work to get on board with the new standard.
    >
    > I appreciate your explaining some of the details of rvalue references
    > and the consequences they can have.


    It isn't perfect, but here is a brief tutorial on the subject:

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

    This paper was meant to be a tutorial for the committee to ease
    concerns on adopting it into the working draft.

    -Howard
    Howard Hinnant, Mar 27, 2011
    #19
  20. K. Frank

    K. Frank Guest

    Hi Howard!

    Thank you for the link to your tutorial.

    On Mar 27, 3:28 pm, Howard Hinnant <> wrote:
    > On Mar 27, 12:39 pm, "K. Frank" <> wrote:
    > ...
    > > On Mar 27, 8:57 am, SG <> wrote:

    > ...
    > > > I should probably mention that there is more to && than its ability to
    > > > bind to temporaries. Not knowing more about && might lead to bad
    > > > surprizes. So, here are some more things you ought to know about
    > > > rvalue references:
    > > > ...

    > ...
    > > This is all very helpful.  I am trying -- step by step, and over
    > > time -- to understand rvalue references and move semantics as I
    > > work to get on board with the new standard.

    > ...
    > It isn't perfect, but here is a brief tutorial on the subject:
    >
    > http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2006/n2027.html
    >
    > This paper was meant to be a tutorial for the committee to ease
    > concerns on adopting it into the working draft.


    This is very helpful. In particular, I appreciate that you include
    some of the "why" along with the "what" (something that is rather
    lacking in the standard).

    This is a good start for me on rvalue references. I should say, I
    am very much looking forward to the new standard becoming official,
    and, of course, to more complete compiler support for it, as well.

    > -Howard


    Best.


    K. Frank
    K. Frank, Mar 27, 2011
    #20
    1. Advertising

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

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. Replies:
    0
    Views:
    851
  2. Eric Anderson

    Bad Transform or Bad Engine?

    Eric Anderson, Oct 4, 2005, in forum: XML
    Replies:
    1
    Views:
    364
    Peter Flynn
    Oct 5, 2005
  3. Kai-Uwe Bux
    Replies:
    19
    Views:
    634
    tom_usenet
    Jun 14, 2004
  4. Replies:
    7
    Views:
    3,181
    James Kanze
    Feb 12, 2008
  5. rantingrick
    Replies:
    44
    Views:
    1,175
    Peter Pearson
    Jul 13, 2010
Loading...

Share This Page