Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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.

like image 681
Charles L Wilcox Avatar asked May 18 '11 22:05

Charles L Wilcox


2 Answers

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.

like image 95
Pavel Minaev Avatar answered Oct 10 '22 09:10

Pavel Minaev


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;
}
like image 28
Martin York Avatar answered Oct 10 '22 08:10

Martin York