views:

362

answers:

1

I have a class similar to boost::any, in that it is a templated container class. I'd like to have a method to write the contained value to a string. However, if the contained type doesn't provide a stream insertion operator, I'd like my method to return some default tring rather than failing to compile. Below is as close as I've come, and should make it clear as to what I'm trying to do:

namespace W {
    namespace hide {
        template <typename T>
        std::ostream& operator<<(std::ostream& out, const T& t) {
            return std::operator<<(out, typeid(T).name());
        }
    }

    template <typename T> struct C {

        T t_;

        std::string ToString() const {
            using namespace hide;
            std::ostringstream oss;
            oss << t_;
            return oss.str();
        }
    };
}

This works pretty well, with some caveats. For example, if I want to actually provide an overloaded insertion operator for a class, then that operator has to either be in the same namespace as the class, or it has to be in the W namespace for it to be considered.

It also has problems with any type that already has a non-member std::operator<<, e.g. char and std::string. If T is one of those types, then the oss << t_ line above becomes ambiguous. This can be worked around by adding overloads for these types inside the W namespace, for example:

std::ostream& operator << (std::ostream& out, const std::string& s) {
    return std::operator <<(out, s);
}

My question is, has anyone found a better method than this? Why do I have to add my own overloads for things like std::string? Is this all supported according to the standard, or am I taking advantage of non-standard behavior? (I am testing with g++ 4.3.3)

+3  A: 

Below is some code that I recall seeing a while ago in a compiler construction class. I thought it was particularly clever (if not 'clean') so I held on to it.

From http://www.cs.colorado.edu/~main/a++/tree.h

   // If data of type T can be printed with the usual << operator, then
   // print<T>(out, p) will interpret *p as a T object and print its
   // value to out.  Otherwise, a message is printed to out, indicating
   // that objects of type T are not printable.
   template<typename T> void print(std::ostream& out, const void* p)
    {
    // The first part of this code sets an enum value, is_printable, to
    // be 1 if the data type T can be printed with the usual <<
    // operator.  Otherwise, is_printable is set to zero.  The programming
    // technique is based on a note from Herb Sutter at
    // http://www.gotw.ca/gotw/071.htm
    class object
    {
    public:
        object(T convert) { };
    };
    char operator << (std::ostream&, const object&);
    enum { is_printable = sizeof(std::cout << (*static_cast<T*>(0))) == sizeof(char) ? 0 : 1 };

        // Notice that the boolean expression in the if-statement is known at
    // compile time, so that only one of the two output statements will be
    // compiled into object code.
    if (is_printable)
        out << *static_cast<const T*>(p);
    else
        out << "(value of type " << typeid(T).name() << " cannot be printed)";
    }

When you construct your container object, hold a pointer to the print function for the variable:

void (*printer)(std::ostream&, const void*); 
printer = print<T>;

Then later use the printer() function to display the contained value if possible.

Hope this helps.

John Ledbetter
JimSteele1222
Actually, scratch that: it picks up stream insertion operators as long as they are in the same namespace as the class T. I can't seem to find a way to, for example, provide a stream insertion operator for vector<bool>.
JimSteele1222