Search code examples
c++comiostreamatl

Printing CComBSTR with iostream (std::wcout)


The following code:

#include <iostream>
#include <atlstr.h>

int main()
{
    CComBSTR bstr(L"test");
    std::wcout << bstr << std::endl;
    std::wcout << static_cast<BSTR>(bstr) << std::endl;
}

prints

033FA16C
test

I tried to investigate which conversions take place in each case with a debugger but both time had stepped into operator BSTR. So why the first line prints address while the second prints a text?


Solution

  • We can remove ATL from this entirely, as it's really a question of how wcout works.

    Consider the following minimal example:

    #include <iostream>
    
    struct Foo
    {
        operator const wchar_t*() const { return L"what"; };
    };
    
    int main()
    {
        Foo f;
        std::wcout << f << std::endl;
        std::wcout << (const wchar_t*)f << std::endl;
    }
    
    // Output:
    //   0x400934
    //   what
    

    (live demo)

    In your example, the implicit conversion from CComBSTR to BSTR is triggered, but not by the template that instantiates operator<<(const wchar_t*) (because the conversion is "user-defined", and user-defined conversions are not considered for template parameter matching). The only viable candidate then is the non-template operator<<(const void*), to which your converted BSTR is passed.

    There's actually a proposal to "fix" this in the standard (LWG 2342) and the text of the proposal explains this in more detail.

    In summary:

    For wide streams argument types wchar_t const* and wchar_t are supported only as template parameters. User defined conversions are not considered for template parameter matching. Hence inappropriate overloads of operator<< are selected when an implicit conversion is required for the argument, which is inconsistent with the behavior for char const* and char, is unexpected, and is a useless result.

    The only remaining viable overload is the one taking const void* and, since every pointer can implicitly convert to const void*, that's what you're getting.