Reputation: 3106
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 tm
s.
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
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
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