JvO
JvO

Reputation: 3106

Using a << operator inside an overloaded << operator

I'm writing a class that overloads the << and >> operator similar to std::istream and std::ostream. The first few functions worked as desired. However, I want to use other std::*stream classes inside my own class for formatting purposes, and this causes an interesting, I would say, 'recursive' compiler issue:

class SmartStream
{
public:
    template <typename F> friend F &operator << (F &in, float f)
    {
        in.type ("f");
        in.m_buf.str ("");
        in.m_buf << f;          // This causes a 'recursive' compile call
        in.add (in.m_buf.str ());
        return in;
    }

protected:
    std::ostringstream m_buf;

    void type(const std::string &_type) {};
    void add(const std::string &) {};
};

The actual error is long but it starts like this:

rec_stream.cpp: In instantiation of 'F& operator<<(F&, float) [with F = std::basic_ostringstream<char>]':
rec_stream.cpp:14:18:   required from 'F& operator<<(F&, float) [with F = ExtraStream]'
rec_stream.cpp:60:11:   required from here
rec_stream.cpp:12:9: error: 'class std::basic_ostringstream<char>' has no member named 'type'
         in.type ("f");
         ^

So apparently the compiler applies the same overloaded << operator to the m_buf variable of type std::ostringstream, but of course that doesn't have type() and add() functions. After reading the answer to this question that kinda makes sense, but doesn't provide a solution.

No before you say, use this in SmartStream:

class SmartStream 
{
   ....
    SmartStream &operator << (float f)
    {
        type("f");
        m_buf.str ("");
        m_buf << f;     // Ambiguous overload
        add( m_buf.str ());
        return *this;
    }
};

There are two problems. First, as noted in the code, that triggers an ambiguous overload. Second, consider the following:

class ExtraStream: public SmartStream
{
public:
    template <typename F> friend F &operator << (F &in, struct tm t)
    {
        in.type ("tm");
        in.m_buf.str ("");
        in.m_buf << t.tm_hour; // Ambiguous overload
        in.add (in.m_buf.str ());
        return in;
    }
};

Indeed, I am extending SmartStream to handle custom types using the mechanism laid out in that class. Using the non-friend operator will not allow me to do this:

ExtraStream es;

struct tm t1, t2;
float f1, f2;

es << f1 << t1 << f2 << t2; // works

Because after a << (float) the return type is SmartStream which does not know how to handle struct tms.

My question:

Is there a way to convince the compiler (gcc 4.8.2) to use the 'base' << operator for std::ostringstream and not the overloaded one? I tried various casts, :: resolution operators, moving the code with in.m_buf << f; to a non-templated function in SmartStream but nothing helped.

Also, why does it only use this inside the template operator << function? Any use of << on std::ostringstream outside of that function works as expected.

Upvotes: 3

Views: 236

Answers (2)

JvO
JvO

Reputation: 3106

I posted this on the gcc buglist since I felt this is a bug or at least an ambiguity in the C++ reference. Jonathan Wakely came up with a surprisingly simple solution:

template <typename F> friend F &operator << (F &in, float f)
{
    in.type ("f");
    in.m_buf.str ("");
    using std::operator <<;  // indicate proper operator in the next line
    in.m_buf << f;          
    in.add (in.m_buf.str ());
    return in;
}

So indeed it required a using clause, just one I wouldn't expect.

Upvotes: 2

Bill Lynch
Bill Lynch

Reputation: 81996

So, I think the simplest thing would be to restrict the friend functions with something like std::enable_if so that they are only valid on streams that have the correct parent class.

#include <sstream>
#include <type_traits>

class SmartStream {
    public:
        template <typename F>
        friend typename std::enable_if<std::is_base_of<SmartStream, F>::value, F&>::type
        operator << (F &in, float f) {
            in.type("f");
            in.m_buf.str("");
            in.m_buf << f;
            in.add (in.m_buf.str ());
            return in;
        }

    protected:
        std::ostringstream m_buf;

        void type(const std::string &_type) {};
        void add(const std::string &) {};
};

class ExtraStream: public SmartStream {
    public:
        template <typename F>
        friend typename std::enable_if<std::is_base_of<ExtraStream, F>::value, F&>::type
        operator << (F &in, struct tm t) {
            in.type ("tm");
            in.m_buf.str ("");
            in.m_buf << t.tm_hour;
            in.add (in.m_buf.str ());
            return in;
        }
};

int main() {
    SmartStream ss;
    ExtraStream es;
    struct tm t;

    ss << 3.14f;
    es << 3.14f << t << 3.14;
    // ss << t; // Fails as expected.
}

Upvotes: 0

Related Questions