Мы можем полностью удалить ATL из этого, так как это действительно вопрос о том, как работает wcout
.
Рассмотрим следующий минимальный пример:
#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
В вашем примере неявное преобразование из CComBSTR
в BSTR
инициируется, но не по шаблону, который создает экземпляр operator<<(const wchar_t*)
(потому что преобразование является «определяемым пользователем», и определяемые пользователем преобразования не учитываются при сопоставлении параметров шаблона).Тогда единственным приемлемым кандидатом является не шаблон operator<<(const void*)
, в который передается ваш преобразованный BSTR
.
На самом деле есть предложение «исправить» это в стандарте ( LWG 2342 ) и текст предложения объясняет это более подробно.
В итоге:
Для широких потоков типы аргументов wchar_t const*
и wchar_t
поддерживаются только в качестве параметров шаблона,Определенные пользователем преобразования не учитываются при сопоставлении параметров шаблона.Следовательно, неподходящие перегрузки operator<<
выбираются, когда для аргумента требуется неявное преобразование, которое не согласуется с поведением для char const*
и char
, является неожиданным и бесполезным результатом.
Единственная оставшаяся жизнеспособной перегрузка - это та, которая принимает const void*
, и, поскольку каждый указатель может неявно преобразовывать в const void*
, это то, что вы получаете.