How to print an object of unknown type
Solution 1
You can provide a templated <<
operator to catch the cases where no custom output operator is defined, since any more specialized versions will be preferred over it. For example:
#include <iostream>
namespace detail
{
template<typename T, typename CharT, typename Traits>
std::basic_ostream<CharT, Traits> &
operator<<(std::basic_ostream<CharT, Traits> &os, const T &)
{
const char s[] = "<unknown-type>";
os.write(s, sizeof(s));
return os;
}
}
struct Foo {};
int main()
{
using namespace detail;
std::cout << 2 << "\n" << Foo() << std::endl;
return 0;
}
will output:
2
<unknown-type>
The detail
namespace
is there to keep this "default" output operator from interfering with code in places other than where it is needed. I.e. you should only use it (as in using namespace detail
) in your dumpKeys()
method.
Solution 2
I originally had just a more canonical way of using Staffan's answer. However, jpalecek correctly pointed out a large flaw with the approach.
As it stood, if no explicit insertion operator is found, the templated insertion operator kicks in and defines a perfect match; this destroys any possibility for existing implicit conversions.
What must be done is make that template insertion operator a conversion (while maintain it's generality), so other conversions can be considered. Once no others are found, then it will be converted to the generic insertion operator.
The utility code is as such:
#include <iosfwd>
#include <memory>
namespace outputter_any_detail
{
// your generic output function
template <typename T>
std::ostream& output_generic(std::ostream& pStream, const T& pX)
{
// note: safe from recursion. if you accidentally try
// to output pX again, you'll get a compile error
return pStream << "unknown type at address: " << &pX;
}
// any type can be converted to this type,
// but all other conversions will be
// preferred before this one
class any
{
public:
// stores a type for later output
template <typename T>
any(const T& pX) :
mPtr(new any_holder<T>(pX))
{}
// output the stored type generically
std::ostream& output(std::ostream& pStream) const
{
return mPtr->output(pStream);
}
private:
// hold any type
class any_holder_base
{
public:
virtual std::ostream& output(std::ostream& pStream) const = 0;
virtual ~any_holder_base(void) {}
};
template <typename T>
class any_holder : public any_holder_base
{
public:
any_holder(const T& pX) :
mX(pX)
{}
std::ostream& output(std::ostream& pStream) const
{
return output_generic(pStream, mX);
}
private:
const T& mX;
any_holder& operator=(const any_holder&);
};
std::auto_ptr<any_holder_base> mPtr;
any& operator=(const any&);
};
// hidden so the generic output function
// cannot accidentally call this fall-back
// function (leading to infinite recursion)
namespace detail
{
// output a type converted to any. this being a conversion allows
// other conversions to partake in overload resolution
std::ostream& operator<<(std::ostream& pStream, const any& pAny)
{
return pAny.output(pStream);
}
}
// a transfer class, to allow
// a unique insertion operator
template <typename T>
class outputter_any
{
public:
outputter_any(const T& pX) :
mX(pX)
{}
const T& get(void) const
{
return mX;
}
private:
const T& mX;
outputter_any& operator=(const outputter_any&);
};
// this is how outputter_any's get outputted,
// found outside the detail namespace by ADL
template <typename T>
std::ostream& operator<<(std::ostream& pStream, const outputter_any<T>& pX)
{
// bring in the fall-back insertion operator
using namespace detail;
// either a specifically defined operator,
// or the generic one via a conversion to any
return pStream << pX.get();
}
}
// construct an outputter_any
template <typename T>
outputter_any_detail::outputter_any<T> output_any(const T& pX)
{
return outputter_any_detail::outputter_any<T>(pX);
}
Stick it in some header like "output_any.hpp"
. And you use it as such:
#include <iostream>
#include "output_any.hpp"
struct foo {};
struct A {};
struct B : A {};
std::ostream& operator<<(std::ostream& o, const A&)
{
return o << "A";
}
int main(void)
{
foo f;
int i = 5;
B b;
/*
Expected output:
unknown type at address: [address]
5
[address]
A
*/ // output via...
std::cout << output_any(f) << std::endl; // generic
std::cout << output_any(i) << std::endl; // int
std::cout << output_any(&i) << std::endl;// void*
std::cout << output_any(b) << std::endl; // const A&
}
Let me know if something doesn't make sense.
gnathan
I am a software design engineer for a networking a telecommunications company. I write a lot of C++, but I'd rather be writing Python. I also play around a lot with software development tools.
Updated on July 03, 2022Comments
-
gnathan almost 2 years
I have a templatized container class in C++ which is similar to a std::map (it's basically a thread-safe wrapper around the std::map). I'd like to write a member function which dumps information about the entries in the map. Obviously, however, I don't know the type of the objects in the map or their keys. The goal is to be able to handle the basic types (integers, strings) and also some specific class types that I am particularly interested in. For any other class, I'd like to at least compile, and preferably do something somewhat intelligent, such as print the address of the object. My approach so far is similar to the following (please note, I didn't actually compile this or anything...):
template<typename Index, typename Entry> class ThreadSafeMap { std::map<Index, Entry> storageMap; ... dumpKeys() { for(std::map<Index, Entry>::iterator it = storageMap.begin(); it != storageMap.end(); ++it) { std::cout << it->first << " => " << it->second << endl; } } ... }
This works for basic types. I can also write custom stream insertion functions to handle specific classes I'm interested in. However, I can't figure out a good way to handle the default case where
Index
and/orEntry
is an unhandled arbitrary class type. Any suggestions?-
GManNickG almost 14 yearsIs nothing an option? If a class isn't made to be outputted, why try to output it?
-
gnathan almost 14 yearsDoing nothing for a class that doesn't support printing would be acceptable. Currently I get a compile error for template instantiations where one of the template parameters doesn't support printing. I'd like to at least compile and not crash for cases where a class doesn't support printing.
-
-
gnathan almost 14 yearsI like your answer. I accepted Staffan's because for my application the simpler implementation seemed sufficient. Unfortunately, I don't have any up-votes yet, or you'd definitely get one. Thanks for your time!
-
BetweenTwoTests almost 14 yearsI stumbled upon your solution, and I found it doesn't work that well. See ideone.com/uRBKl, it doesn't correctly handle the
int*
andB
. -
BetweenTwoTests almost 14 yearsAFAIK, this prints "unknown-type" even for types that are, in fact, known. See my comment on GMan's solution.
-
GManNickG almost 14 years@jpalecek: While I think you've made an important point, I want to correct your terminology: you say "this prints "unknown-type" even for types that are, in fact, known" but neither
B
orint*
have insertion operations; that is, they aren't "known". What you mean to say is "the solution, because it uses templates, prefers the generic solution over any conversions", which explains it much better.B
can be converted toA
, but the template insertion operation matches better, andint*
can be converted tovoid*
, but again the template insertion operation matches better. I will fix it. :) -
GManNickG almost 14 years@jpalecek: That should fix it. Making a template function into a template class + conversion makes the code a bit verbose, but it's not too hard and doesn't need to be looked at often anyway. :)
-
BetweenTwoTests almost 14 yearsOh yes, I could have realized it. I was trying to make this into something that could be used for SFINAE, and urged not to use the extra namespace - which was a bad idea. With the extra namespace, it is easy: ideone.com/xzf7S