Problems with derived IO classes

Discussion in 'C++' started by Ian C, Apr 12, 2007.

  1. Ian C

    Ian C Guest

    Hello all

    Apologies if this is a dumb question, but this is driving me nuts, and
    I'm really an old procedural programmer so not sure if it's me or the
    compiler at fault. I can perhaps guess :)

    I have written a derived streambuf class so I can log to a
    system-dependent logging interface. I use this in a derived class like so:

    class Syslog : public std::eek:stream
    {
    public:
    Syslog(eSyslogLevel level)
    : std::eek:stream(new SyslogBuffer(level))
    {
    }
    };


    If I use a variable to use this class, it compiles fine:

    Syslog str(SYSLOG_INFO);

    str << "Test Number " << 1 << std::endl;


    However, if I use it as a temporary object like this:

    Syslog(SYSLOG_INFO) << "Test Number " << 1 << std::endl;

    the compiler complains that it can't find a match for operator<< for the
    Syslog class and the character array.

    Am I going mad, or just doing something dumb?

    Cheers.


    Ian C
     
    Ian C, Apr 12, 2007
    #1
    1. Advertising

  2. Ian C

    red floyd Guest

    Ian C wrote:
    > Hello all
    >
    > Apologies if this is a dumb question, but this is driving me nuts, and
    > I'm really an old procedural programmer so not sure if it's me or the
    > compiler at fault. I can perhaps guess :)
    >
    > I have written a derived streambuf class so I can log to a
    > system-dependent logging interface. I use this in a derived class like so:
    >
    > class Syslog : public std::eek:stream
    > {
    > public:
    > Syslog(eSyslogLevel level)
    > : std::eek:stream(new SyslogBuffer(level))
    > {
    > }
    > };
    >
    >
    > If I use a variable to use this class, it compiles fine:
    >
    > Syslog str(SYSLOG_INFO);
    >
    > str << "Test Number " << 1 << std::endl;
    >
    >
    > However, if I use it as a temporary object like this:
    >
    > Syslog(SYSLOG_INFO) << "Test Number " << 1 << std::endl;
    >
    > the compiler complains that it can't find a match for operator<< for the
    > Syslog class and the character array.
    >


    I believe the difference is that in the second case, Syslog(SYSLOG_INFO)
    is a temporary, and therefore binds to a const reference, and there is
    no operator<< that takes a const ostream& as the LHS.

    BTW, you do know you can change the streambuf in a regular ostream, so
    you don't have to derive from std::eek:stream, right?
     
    red floyd, Apr 12, 2007
    #2
    1. Advertising

  3. Ian C wrote:
    > Apologies if this is a dumb question, but this is driving me nuts, and
    > I'm really an old procedural programmer so not sure if it's me or the
    > compiler at fault. I can perhaps guess :)
    >
    > I have written a derived streambuf class so I can log to a
    > system-dependent logging interface. I use this in a derived class
    > like so:
    > class Syslog : public std::eek:stream
    > {
    > public:
    > Syslog(eSyslogLevel level)
    > : std::eek:stream(new SyslogBuffer(level))
    > {
    > }
    > };
    >
    >
    > If I use a variable to use this class, it compiles fine:
    >
    > Syslog str(SYSLOG_INFO);
    >
    > str << "Test Number " << 1 << std::endl;
    >
    >
    > However, if I use it as a temporary object like this:
    >
    > Syslog(SYSLOG_INFO) << "Test Number " << 1 << std::endl;
    >
    > the compiler complains that it can't find a match for operator<< for
    > the Syslog class and the character array.
    >
    > Am I going mad, or just doing something dumb?


    The operator that it can find (and uses for the former case, with
    the 'str' variable) is probably a non-member, and it has the first
    argument a reference to non-const ostream. You cannot bind a non-
    const reference to a temporary (in your case 'Syslog(SYSLOG_INFO)'
    is the temporary).

    There is a trick you can play to get where you want to be:

    Syslog(SYSLOG_INFO) << std::flush << "Test Number" << 1 << std::endl;

    What it does is calling a member function of 'ostream' which then
    returns a non-const reference (to the same temporary, but it's OK
    since the temporary survives until the end of the full expression),
    and then it can re-use the same non-const reference to call the
    non-member operator <<.

    V
    --
    Please remove capital 'A's when replying by e-mail
    I do not respond to top-posted replies, please don't ask
     
    Victor Bazarov, Apr 12, 2007
    #3
  4. Ian C

    Ian C Guest

    red floyd wrote:
    > Ian C wrote:
    >>
    >> [snip]
    >> If I use a variable to use this class, it compiles fine:
    >>
    >> Syslog str(SYSLOG_INFO);
    >>
    >> str << "Test Number " << 1 << std::endl;
    >>
    >>
    >> However, if I use it as a temporary object like this:
    >>
    >> Syslog(SYSLOG_INFO) << "Test Number " << 1 << std::endl;
    >>
    >> the compiler complains that it can't find a match for operator<< for
    >> the Syslog class and the character array.
    >>

    >
    > I believe the difference is that in the second case, Syslog(SYSLOG_INFO)
    > is a temporary, and therefore binds to a const reference, and there is
    > no operator<< that takes a const ostream& as the LHS.
    >


    Ah! Thanks for that -- you learn something new everyday!

    > BTW, you do know you can change the streambuf in a regular ostream, so
    > you don't have to derive from std::eek:stream, right?


    I was making it so that the destruction of the streambuf flushed out
    output to the logging interface, and the second method lust looked like
    a neat way of implementing logging.

    I have seen similar logging classes where different IO classes are
    returned from member functions within a container and allocated at
    start-up, but that could get rather messy as I'm in a multi-threaded
    application, so the individual temporary object method seemed ideal.

    Well, would have done had it been valid :)

    Thanks for the help, red.


    Ian C
     
    Ian C, Apr 12, 2007
    #4
  5. Ian C

    Ian C Guest

    Victor Bazarov wrote:
    > Ian C wrote:
    >> [snip]
    >> If I use a variable to use this class, it compiles fine:
    >>
    >> Syslog str(SYSLOG_INFO);
    >>
    >> str << "Test Number " << 1 << std::endl;
    >>
    >>
    >> However, if I use it as a temporary object like this:
    >>
    >> Syslog(SYSLOG_INFO) << "Test Number " << 1 << std::endl;
    >>
    >> the compiler complains that it can't find a match for operator<< for
    >> the Syslog class and the character array.
    >>
    >> Am I going mad, or just doing something dumb?

    >
    > The operator that it can find (and uses for the former case, with
    > the 'str' variable) is probably a non-member, and it has the first
    > argument a reference to non-const ostream. You cannot bind a non-
    > const reference to a temporary (in your case 'Syslog(SYSLOG_INFO)'
    > is the temporary).
    >
    > There is a trick you can play to get where you want to be:
    >
    > Syslog(SYSLOG_INFO) << std::flush << "Test Number" << 1 << std::endl;
    >
    > What it does is calling a member function of 'ostream' which then
    > returns a non-const reference (to the same temporary, but it's OK
    > since the temporary survives until the end of the full expression),
    > and then it can re-use the same non-const reference to call the
    > non-member operator <<.
    >


    Thanks to you too, Victor. Never thought of temporary objects being
    const, but I suppose it makes some sense.

    I think I'll have to have a rethink. I may just make the streambuf
    complete the logging operation when it receives a newline (rather than
    on destruction as it does at the minute), so that users can just
    instantiate the appropriate logging objects when they start up and carry
    on using it.

    Thanks again Victor.

    > V



    Ian C
     
    Ian C, Apr 12, 2007
    #5
  6. Ian C

    Pete Becker Guest

    Ian C wrote:
    >
    > Thanks to you too, Victor. Never thought of temporary objects being
    > const, but I suppose it makes some sense.
    >


    Victor was very careful not to say that temporary objects are const.
    They're not. Well, sort of. The rule is that you can't bind a temporary
    object to a non-const reference. So this isn't valid:

    struct T
    {
    void func();
    }

    void f(T&);

    f(T()); // ill-formed: attempts to call f with temporary

    Granted, that looks a lot like a const object. But you can call
    non-const member functions on a temporary object:

    T().func(); // okay

    which is why the trick that Victor showed works. The inserter for char*
    is not a member function, so you can't call it on the temporary stream
    object. The inserter for io manipulators is a member function, so
    calling it on the temporary is okay. And once the stream object has been
    laundered through the inserter for manipulators, you're no longer trying
    to bind a temporary to anything, so the rest of the manipulators, both
    member and non-member, are okay.

    --

    -- Pete
    Roundhouse Consulting, Ltd. (www.versatilecoding.com)
    Author of "The Standard C++ Library Extensions: a Tutorial and
    Reference." (www.petebecker.com/tr1book)
     
    Pete Becker, Apr 12, 2007
    #6
  7. Ian C

    Ian C Guest

    Pete Becker wrote:
    > Victor was very careful not to say that temporary objects are const.
    > They're not. Well, sort of. The rule is that you can't bind a temporary
    > object to a non-const reference. So this isn't valid:
    >
    > struct T
    > {
    > void func();
    > }
    >
    > void f(T&);
    >
    > f(T()); // ill-formed: attempts to call f with temporary
    >
    > Granted, that looks a lot like a const object. But you can call
    > non-const member functions on a temporary object:
    >
    > T().func(); // okay
    >
    > which is why the trick that Victor showed works. The inserter for char*
    > is not a member function, so you can't call it on the temporary stream
    > object. The inserter for io manipulators is a member function, so
    > calling it on the temporary is okay. And once the stream object has been
    > laundered through the inserter for manipulators, you're no longer trying
    > to bind a temporary to anything, so the rest of the manipulators, both
    > member and non-member, are okay.
    >


    Apologies to Victor and thanks for the clarification Pete -- I realised
    shortly afterwards they couldn't be const just because they're temporary
    as I'd already tried something like:

    Constructor().give_me_the_stream() << "Hello";

    while messing around...

    It does ring a definite bell now, and I can remember being told a good
    numbers of years ago (when I last had a brush with C++) about not using
    temporary objects for interfaces with a non-const reference parameter.

    I just didn't realise that you could circumvent it by calling a
    non-const member that returned the same object, which is where my
    confusion came in. Assuming I have actually finally got it straight
    this time :)

    Thanks for the help.


    Ian C
     
    Ian C, Apr 12, 2007
    #7
  8. Ian C

    Pete Becker Guest

    Ian C wrote:
    >
    > It does ring a definite bell now, and I can remember being told a good
    > numbers of years ago (when I last had a brush with C++) about not using
    > temporary objects for interfaces with a non-const reference parameter.
    >
    > I just didn't realise that you could circumvent it by calling a
    > non-const member that returned the same object, which is where my
    > confusion came in. Assuming I have actually finally got it straight
    > this time :)
    >


    Yup, you've got it straight. That's a clever trick, one that I haven't
    seen before.

    --

    -- Pete
    Roundhouse Consulting, Ltd. (www.versatilecoding.com)
    Author of "The Standard C++ Library Extensions: a Tutorial and
    Reference." (www.petebecker.com/tr1book)
     
    Pete Becker, Apr 12, 2007
    #8
    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. Colin Goudie
    Replies:
    6
    Views:
    473
    Victor Bazarov
    Jan 26, 2004
  2. Manuel
    Replies:
    8
    Views:
    640
    Manuel
    Jan 5, 2006
  3. Replies:
    6
    Views:
    374
  4. Replies:
    1
    Views:
    397
    myork
    May 23, 2007
  5. Replies:
    1
    Views:
    389
    Victor Bazarov
    May 23, 2007
Loading...

Share This Page