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/or Entry
is an unhandled arbitrary class type. Any suggestions?
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With