I'm implementing a stream insertion operator for a class of mine. I'd like my class to work with both narrow and wide streams. I'm using a template to allow this kind of behavior -- and everything is independent of which stream type is actually used, with the exception of character literals. If it's a wide string, the character literals need L
prepended to the literal, otherwise they do not.
Is there a way to key this sort of thing to the template parameter so that I don't need to duplicate so much code on this?
(I would prefer to avoid performing narrow-to-wide character or wide-to-narrow character conversions at runtime if possible.)
Example of what I currently have -- it's a template but it won't work with narrow character streams because of the wide character literals:
template <typename charT, typename traits>
std::basic_ostream<charT, traits>& operator<<(
std::basic_ostream<charT, traits>& lhs,
const Process& rhs
)
{
lhs << L"Process (0x" << std::setw(8) << std::hex
<< std::setfill(L'0') << rhs.GetId() << L") ";
lhs << rhs.GetName() << std::endl;
lhs << L"Command Line: " << rhs.GetCmdLine() << std::endl;
const std::vector<Thread>& threads = rhs.GetThreads();
for (std::vector<Thread>::const_iterator it = threads.begin();
it != threads.end(); ++it)
{
lhs << L" --> " << *it << std::endl;
}
const std::map<void *, Module>& modules = rhs.GetModules();
for (std::map<void *, Module>::const_iterator it = modules.begin();
it != modules.end(); ++it)
{
lhs << L" --> " << it->second << std::endl;
}
return lhs;
}
If you don't want the runtime overhead, I think that, although ugly, a macro can help you in this case.
template <typename T>
inline const T* select(const char* narrow, const wchar_t* wide);
template <>
inline const char* select<char>(const char* narrow, const wchar_t* /*wide*/)
{
return narrow;
}
template <>
inline const wchar_t* select<wchar_t>(const char* /*narrow*/, const wchar_t* wide)
{
return wide;
}
#define doselect(T, str) select<T>(str, L ## str)
template <typename charT, typename traits>
std::basic_ostream<charT, traits>& operator<<(
std::basic_ostream<charT, traits>& lhs,
const Process& rhs
)
{
lhs << doselect(charT, "Process (0x") << std::setw(8) << std::hex
<< std::setfill(charT('0')) << rhs.GetId() << doselect(charT, ") ");
lhs << rhs.GetName() << std::endl;
lhs << doselect(charT, "Command Line: ") << rhs.GetCmdLine() << std::endl;
const std::vector<Thread>& threads = rhs.GetThreads();
for (std::vector<Thread>::const_iterator it = threads.begin();
it != threads.end(); ++it)
{
lhs << doselect(charT, " --> ") << *it << std::endl;
}
const std::map<void *, Module>& modules = rhs.GetModules();
for (std::map<void *, Module>::const_iterator it = modules.begin();
it != modules.end(); ++it)
{
lhs << doselect(charT, " --> ") << it->second << std::endl;
}
return lhs;
}
You can probably expand doselect
with another nice macro, to further reduce code duplication. i.e. doselect2(" --> ")
would automatically expand to doselect(charT, " --> ")
.
What I do is to only insert char, and to convert them to either wide or narrow char. The conversion is done during the compilation, and if you only use characters that are equal in wide and narrow encoding it does work. This is tiresome, but it does work.
template <typename charT, typename traits>
std::basic_ostream<charT, traits>& operator<<(
std::basic_ostream<charT, traits>& lhs,
const Process& rhs
)
{
lhs << charT('P') << charT('r') << charT('o') << charT('c') << charT('e')
<< charT('s') << charT('s') << charT(' ') << charT('(') << charT('0')
<< charT('x') << std::setw(8) << std::hex << std::setfill(charT('0'))
<< rhs.GetId() << charT(')') << charT(' ');
lhs << rhs.GetName() << std::endl;
lhs << charT('C') << charT('o') << charT('m') << charT('m') << charT('a')
<< charT('n') << charT('d') << charT(' ') << charT('L') << charT('i')
<< charT('n') << charT('e') << charT(':') << charT(' ')
<< rhs.GetCmdLine() << std::endl;
const std::vector<Thread>& threads = rhs.GetThreads();
for (std::vector<Thread>::const_iterator it = threads.begin();
it != threads.end(); ++it)
{
lhs << charT(' ') << charT('-') << charT('-') << charT('>') << charT(' ')
<< *it << std::endl;
}
const std::map<void *, Module>& modules = rhs.GetModules();
for (std::map<void *, Module>::const_iterator it = modules.begin();
it != modules.end(); ++it)
{
lhs << charT(' ') << charT('-') << charT('-') << charT('>') << charT(' ')
<< it->second << std::endl;
}
return lhs;
}
If you have many strings, you can use another template, that you'll specialize on the wide or narrow character type and use to store the strings. This however will force you to duplicate your strings (and you'll fail the DRY principle).
template <typename charT>
struct ProcessInsertionOperatorHelper
{
static const charT* const String1;
static const charT* const String2;
static const charT* const String3;
static const charT* const String4;
};
template <>
const wchar_t* const ProcessInsertionOperatorHelper<wchar_t>::String1 = L"Process (0x";
template <>
const wchar_t* const ProcessInsertionOperatorHelper<wchar_t>::String2 = L") ";
template <>
const wchar_t* const ProcessInsertionOperatorHelper<wchar_t>::String3 = L"Command Line: ";
template <>
const wchar_t* const ProcessInsertionOperatorHelper<wchar_t>::String4 = L" --> ";
template <>
struct ProcessInsertionOperatorHelper<char>
{
};
template <typename charT, typename traits>
std::basic_ostream<charT, traits>& operator<<(
std::basic_ostream<charT, traits>& lhs,
const Process& rhs
)
{
lhs << ProcessInsertionOperatorHelper<charT>::String1 << std::setw(8)
<< std::hex << std::setfill(L'0') << rhs.GetId()
<< ProcessInsertionOperatorHelper<charT>::String2;
lhs << rhs.GetName() << std::endl;
lhs << ProcessInsertionOperatorHelper<charT>::String3
<< rhs.GetCmdLine() << std::endl;
const std::vector<Thread>& threads = rhs.GetThreads();
for (std::vector<Thread>::const_iterator it = threads.begin();
it != threads.end(); ++it)
{
lhs << ProcessInsertionOperatorHelper<charT>::String4
<< *it << std::endl;
}
const std::map<void *, Module>& modules = rhs.GetModules();
for (std::map<void *, Module>::const_iterator it = modules.begin();
it != modules.end(); ++it)
{
lhs << ProcessInsertionOperatorHelper<charT>::String4
<< it->second << std::endl;
}
return lhs;
}
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