Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How does one write a stream insertion operator for the general case? (That is, for both `char` and `wchar_t` streams?)

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;
}
like image 375
Billy ONeal Avatar asked Jan 20 '23 20:01

Billy ONeal


2 Answers

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, " --> ").

like image 60
dalle Avatar answered Jan 30 '23 00:01

dalle


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;
}
like image 38
Sylvain Defresne Avatar answered Jan 30 '23 00:01

Sylvain Defresne