How to Generically Define Insertion Operators for all C++ IOStream Manipulators?
Asked Answered
P

2

6

All,

Why does the following code fail to compile for 'std::endl', but it's fine for all of the other inserted types?

#include <sstream> // ostringstream

/// @brief A class that does streamed, formatted output via 'operator<<'.
class My_Stream
{
public:
    /// @brief A member method that manipulates the underlying stream.
    void foo()
    {
        m_oss << "foo_was_here; ";
    }

private:
    /// @brief The underlying stream.
    std::ostringstream m_oss;

    /// @brief 'operator<<' is a friend.
    template< typename T >
    friend My_Stream& operator<<( My_Stream& a_r_my_stream,
                                  const T& a_r_value );
};

/// @brief A manipulator that calls a class method.
My_Stream& manipulator_foo( My_Stream& a_r_my_stream )
{
    a_r_my_stream.foo();
    return a_r_my_stream;
}

/// @brief The generic insertion operator.
template< typename T >
My_Stream& operator<<( My_Stream& a_r_my_stream,
                       const T& a_r_value )
{
    a_r_my_stream.m_oss << a_r_value;
    return a_r_my_stream;
}

/// @brief Define an iostream-like manipulator for my-stream.
typedef My_Stream& ( * my_stream_manipulator ) ( My_Stream& );

/// @brief The specialized 'my_stream_manipulator' insertion operator.
template<>
My_Stream& operator<<( My_Stream& a_r_my_stream,
                       const my_stream_manipulator& a_r_manipulator )
{
    return a_r_manipulator( a_r_my_stream );
}

int main( int argc, char* argv[] )
{
    My_Stream my_stream;

    my_stream << 'c'; // char
    my_stream << "string"; // c-string
    my_stream << 1u; // unsigned int
    my_stream << -1; // signed int
    my_stream << 5.3f; // float
    my_stream << -23.345; // double
    my_stream << std::boolalpha; // std::ios_base manipulator
    my_stream << std::endl; // std::ostream manipulator
    my_stream << manipulator_foo; // my_stream manipulator

    return 0;
}

I get the following G++ 4.5 error:

willo:~/test_cpp$ g++ -Wall test_overloaded_insertion_manipulators.cpp test_overloaded_insertion_manipulators.cpp: In function ‘int main(int, char**)’: test_overloaded_insertion_manipulators.cpp:60: error: no match for ‘operator<<’ in ‘my_stream << std::endl’

I expect the code to instantiate a 'operator<<' for std::endl, just like it did for the primitives, std::ios_base and my custom manipulator.

For context, I'm trying to create a light-API IOStream-like class that works with current IOStream manipulators, as well as one or two more custom manipulators.

Pushy answered 18/5, 2011 at 22:44 Comment(3)
Are you sure it wouldn't be more practical to just publicly inherit from ostringstream? You could still add custom functionality, but wouldn't need to bother to get what stringstream itself already can do.Latecomer
@leftaroundabout: most STL objects aren't really designed to be derived from but std::ostringstream does seem to at least have a virtual destructor so that might work.Crotty
@Latecomer Yes, I'm sure. This is a toy example to debug my current problem. My intended code must intercept each insertion operation, and conditionally do things; so I must re-implement 'operator<<' against my new class.Pushy
R
8

Because endl is a function template:

template <class charT, class traits>
basic_ostream<charT,traits>& endl(basic_ostream<charT,traits>& os);

So the identifier itself is not a value. It only becomes a value (function pointer) when it's instantiated. But your operator<< is itself a template, and there is no type information available to the compiler to decide which types to instantiate endl with.

In contrast, e.g. boolalpha is:

ios_base& boolalpha(ios_base& str);

Hence why it works.

endl works for basic_ostream, because that one defines operator<< overloads as member functions taking function pointers; in particular:

basic_ostream<charT,traits>& operator<<(basic_ostream<charT,traits>& (*pf)(basic_ostream<charT,traits>&));

So in a call like stream << endl, it would know charT and traits from type of this (i.e. left side of operator), and that would give it exact type of function pointer to expect on the right side - which it would then use to instantiate the corresponding version of endl. You can do the same for your class.

Rennet answered 18/5, 2011 at 23:13 Comment(0)
A
2

You need to define stream manipulators as a separate friend.

Add this friend:

// Note: Untested (don't have a compiler here).
template <class charT, class Traits>
friend My_Stream& operator<<( My_Stream&, std::basic_ostream<charT, Traits>& (*)(std::basic_ostream<charT, Traits>&));

Then you need to define the function:

template <class charT, class Traits>
My_Stream& operator<<( My_Stream& stream, std::basic_ostream<charT, Traits>& (*manip)(std::basic_ostream<charT, Traits>&))
{
    (*manip)(stream.m_oss);
    return stream;
}
Angwantibo answered 18/5, 2011 at 23:15 Comment(5)
Should this really be templated? Since stream.m_oss is a specific type, only std::ostream& (*)(std::ostream&) would actually work here.Mehitable
The template is not for My_Stream but for std::endl which is the template that we are worried about (Which is why we have the problem in the first place (so yes you need the template)).Angwantibo
+1 this is better than my extension on his template specialization as it works for all the template types of std::endl that could be defined.Crotty
No, the extra operator<< needs to not be a template, for reasons @Pavel explained. codepad.org/VKIghrZzMehitable
@Mehitable You're correct in one sense; any IOStream manipulators I want to handle will be part of 'ostringstream's hierarchy, such as 'ostream', 'ios' and 'ios_base'. I'm considering explicit overrides of 'operator<<' for each of those argument types, and then have a separate, generic 'T' version.Pushy

© 2022 - 2024 — McMap. All rights reserved.