1

I have a derived basic_ostream class and an inline modifier (similar to setw). My stream class should also inherit all the operator << behavior from its parent. I get different compiler errors depending on whether I use the "using" keyword or not:

#include <iostream>

struct modifier { };

template <typename C, typename T=std::char_traits<C> >
struct mystream : public std::basic_ostream<C, T>
{
    // this is where the trouble is
    using std::basic_ostream<C, T>::operator <<;

    inline mystream & operator << (const modifier & mod)
    {
        // ...custom behavior...
        return *this;
    }
};

int main()
{
    mystream<char> foo;
    modifier m;
    foo << "string";  // this fails if the using is present
    foo << 123;       // this fails if the using is absent
    foo << m;
}

When I put the using directive in, the compiler is confused about the "string" output, and if I comment it out, it gets confused about the integer 123 output, in both cases giving me "error: ambiguous overload for 'operator<<'". I have the problem with both g++ 4.2.1 and g++4.8. What's the right way forward here?

John S
  • 3,035
  • 2
  • 18
  • 29
  • Why are you deriving from `basic_ostream`? What are you doing that a non-member `operator <<` cannot do? – n. m. could be an AI May 03 '13 at 16:15
  • I commented below--my stream has a lot of custom behavior, including its own streambuf, etc., and that operator needs to change the state of the stream. – John S May 03 '13 at 16:22
  • Use `xalloc` and `iword` / `pword`, they are specifically designed for maintaining custom stream state. You *can* derive from `basic_ostream` if you want to construct something with your own `streambuf` (`fstream` does just that), just don't add any `operator<<` members to it. – n. m. could be an AI May 03 '13 at 16:29

2 Answers2

3

Rather then inherit from std::basic_ostream, won't it be sufficient to just re-implement << for your modifier struct using a regular stream:

std::ostream & operator << (std::ostream &stream, const modifier & mod)
{
    // ...custom behavior...
    return stream;
}

Your solution seems overcomplicated, but I think the actual error you get comes from your overload of << - it has to accept two arguments (first argument being reference to stream itself).

Ilya Kobelevskiy
  • 5,245
  • 4
  • 24
  • 41
  • My stream has a lot of custom behavior, including its own streambuf, etc. That operator needs access to my stream to change its state. Are you suggesting moving the operator out to global scope and giving it friend access to the stream? Does that solve the other problem? – John S May 03 '13 at 16:17
  • Ok, then inline mystream & operator << (const modifier & mod) simply won't work, you need inline mystream & operator << (mystream &stream, const modifier & mod), and do not operate on this pointer inside the function but on stream passed as argument - since operator << needs to accept two arguments to be able to chain << operators. Try that and see if there will be any other errors. – Ilya Kobelevskiy May 03 '13 at 16:23
  • And yes, it has to be in the global scope, give it friend access if you need to access private members - it cannot be member operator accepting single argument since then things like foo <<"test" << 123; won't work... – Ilya Kobelevskiy May 03 '13 at 16:26
  • His `operator<<` does take two arguments. – James Kanze May 03 '13 at 17:07
  • Note that the `operator<<` _must_ take an `std::ostream&` as its first argument. _Not_ a `mystream&`, since the results of `foo << 123` will still be an `std::ostream&`, even if `foo` has type `std::istream&`. If you need to access special information in `mystream` from your `<<`, you'll have to use `dynamic_cast` to get it (and deal with the runtime errors). – James Kanze May 03 '13 at 17:32
  • @James Kanze - not exactly true, see - http://coliru.stacked-crooked.com/view?id=42254fa5bf22d76947b67ed0a83aa43b-7063104e283ed82d51a6fde7370c6e59 – Ilya Kobelevskiy May 03 '13 at 18:26
  • @IlyaKobelevskiy Which only works generally if you replace _all_ of the `<<` operators (in which case, there's no point in deriving from `std::ostream`). Including the user defined ones you don't know about. I've done this (my `oxdrstream`), but it derives from `std::basic_ios`, and not `std::ostream` (and client code _does_ have to define an `<<` operator for it, since it has different requirements than an `std::ostream`). – James Kanze May 04 '13 at 11:30
2

Without the using, it is clear: the compiler will not find any of the member overloads of <<, because your function hides them. The << is a member, so without the using, it disappears. The << is not a member, so it still works.

When you add the using: all of the member overloads are visible, as if they were members of your class. And "string" will be converted to a char const*. The overload that the compiler is trying to resolve is:

operator<<( mystream<char>, char const* ).

Now consider some of the overloads to be considered:

std::ostream& mystream::operator<<( void const* );
std::ostream& mystream::operator<<( bool );
std::ostream& operator<<( std::ostream&, char const* );

For the first argument (foo, a mystream), the first two functions are both better matches than the third (since they are an exact match); for the second argument (the string literal), the third function is a better match. Thus: ambiguous.

More generally, there are several problems with your code. Fundamentally, you do not add << operators by deriving. As you see, it doesn't work. Perhaps more significantly, something like:

foo << 123 << m;

will not work, because foo << 123 returns a std::ostream&, not a mystream, and there is no << which will work with an std::ostream& and a modifier. You add << operators by defining new free functions:

std::ostream&
operator<<( std::ostream& dest, modifier const& other )
{
    // ...
    return *this;
}

If you need additional data to format, you use xalloc and iword or pword to get it, e.g. to define a manipulator:

static int Modifier::modIndex = std::ostream::xalloc();

class mod1
{
    int myModifier;
public: 
    mod1( int m ) : myModifier( m ) {}
    friend std::ostream& operator<<( std::ostream& dest,
                                     mod1 const& mod )
    {
        dest.iword( modIndex ) = myModifier;
        return *this;
    }
};

You then access dest.iword( modIndex ) to get this information in the output routine.

iword() returns a long&, different for each instance of your stream (and for each different index you use).

If you need more information, you can use pword instead of iwordpword returns a reference to a void*. If you want use it to point to dynamically allocated memory, don't forget to register a callback to delete it (using ios_base::register_callback).

James Kanze
  • 150,581
  • 18
  • 184
  • 329