0

While searching for any kind of function in could help printing data to debug console, I've found this function here on StackOverflow that can print almost any kind of data:

template <typename Arg, typename... Args>
void doPrint(Arg&& arg, Args&&... args)
{
    std::stringstream out;

    out << std::forward<Arg>(arg);
    using expander = int[];
    (void)expander {
        0, (void(out << std::left << std::setw(20) << std::forward<Args>(args)), 0)...
    };

    OutputDebugStringA(out.str().c_str());
}

But it doesn't work when I try to pass a wstring to it, ex:

std::wstring color = L"blue";
doPrint("color: ", color);

I get this error:

Error    C2679    binary '<<': no operator found which takes a right-hand operand of type 'std::wstring' (or there is no acceptable conversion)

Is it possible to also support wstring?

I tried to change std::stringstream to std::wstringstream, but now I get error on OutputDebugStringA():

void OutputDebugStringA(LPCSTR)': cannot convert argument 1 from 'std::basic_string<wchar_t,std::char_traits<wchar_t>,std::allocator<wchar_t>>' to 'LPCSTR'
Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
Cesar
  • 41
  • 2
  • 5
  • 16

1 Answers1

1

By default, you can't print a std::wstring to a char-based std::ostream, such as std::stringstream, because std::basic_ostream<char> (aka std::ostream) does not have an overloaded operator<< defined for std::wstring (or const wchar_t*).

You could define your own operator<< for that purpose, which converts a std::wstring to a std::string/char* using WideCharToMultiByte() or equivalent conversion, and then print that instead, eg:

std::ostream& operator<<(std::ostream &os, const std::wstring &wstr)
{
    int len = WideCharToMultiByte(CP_ACP, 0, wstr.c_str(), wstr.size(), nullptr, 0, nullptr, nullptr);
    if (len > 0) {
        std::string str;
        str.resize(len);
        WideCharToMultiByte(CP_ACP, 0, wstr.c_str(), wstr.size(), str.data()/* or: &str[0] */, len, nullptr, nullptr);
        os << str;
    }
    return os;
}

template <typename Arg, typename... Args>
void doPrint(Arg&& arg, Args&&... args)
{
    std::stringstream out;

    out << std::forward<Arg>(arg);
    using expander = int[];
    (void)expander {
        0, (void(out << std::left << std::setw(20) << std::forward<Args>(args)), 0)...
    };

    OutputDebugStringA(out.str().c_str());
}

Online Demo

Otherwise, you need a wchar_t-based std::basic_ostream<wchar_t> (aka std::wostream) instead, such as std::wstringstream, using OutputDebugStringW() for the logging (unless you convert the output std::wstring to std::string to log with OutputDebugStringA()). Fortunately, std::basic_ostream<CharT> does have an overloaded operator<< for const char* strings (but not std::string) regardless of whether CharT is char or wchar_t, so std::wstringstream can print the pointer returned by std::string::c_str(), eg:

std::wostream& operator<<(std::wostream &os, const std::string &str)
{
    os << str.c_str();
    return os;
}

template <typename Arg, typename... Args>
void doPrint(Arg&& arg, Args&&... args)
{
    std::wostringstream out;

    out << std::forward<Arg>(arg);

    using expander = int[];
    (void)expander {
        0, (void(out << std::left << std::setw(20) << std::forward<Args>(args)), 0)...
    };

    OutputDebugStringW(out.str().c_str());
}

Online Demo

Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
  • I'm getting some errors, In the first example: `argument of type "const char *" is incompatible with parameter of type "LPSTR"` and in the second example, `identifier "arg" is undefined` – Cesar Aug 12 '22 at 01:19
  • The "incompatible" error is because you are using an version of C++ that predates C++17, where `std::string::data()` is overloaded to return a non-const `char*`. In which case, you can use `&str[0]` instead. The "identifier undefined" error was a typo on my part, I have fixed it. – Remy Lebeau Aug 12 '22 at 01:29
  • Remy, how do I declare the template `doPrint` into the `.h` file to be able to access it in others `.cpp` files? I have tried adding into the `.h`: `template ` `void doPrint(Arg&& arg, Args&&... args);` but i got compile errors: `Error LNK2001 unresolved external symbol "void __cdecl doPrint(char const (&)[14],int &)" (??$doPrint@AEAY0O@$$` – Cesar Aug 12 '22 at 21:57
  • @Katia see [Why can templates only be implemented in the header file?](https://stackoverflow.com/questions/495021/) – Remy Lebeau Aug 12 '22 at 22:19
  • I read it but im more confuse than before, i tried now: `template struct test { void doPrint(Arg&& arg, Args&&... args); }` – Cesar Aug 12 '22 at 22:33
  • @Katia you can't *declare* a template in a header file and then *define* it in a .cpp file. Templates don't work that way. That is why you are getting "unresolved external" errors and such. The template has to be *defined* inside the same translation unit that *declares* it. You *can* separate the declaration and definition, as long as they are compiled together in the same translation unit, ie they exist in the same header file, or the definition is in a separate file that is `#include`'d by the header file. Otherwise, the template should be *declared* and *defined* as an inline function. – Remy Lebeau Aug 12 '22 at 22:52