I discussed in a previous blog post a subtle bug involving CStringW and wcout, and later I showed how to fix it.
In this blog post, I’d like to discuss in more details what’s happening under the hood, and what triggers that bug.
Well, to understand the dynamics of that bug, you can consider the following simplified case of a function and a function template, implemented like this:
void f(const void*) { cout << "f(const void*)\n"; } template <typename CharT> void f(const CharT*) { cout << "f(const CharT*)\n"; }
If s is a CStringW object, and you write f(s), which function will be invoked?
Well, you can write a simple compilable code containing these two functions, the required headers, and a simple main implementation like this:
int main() { CStringW s = L"Connie"; f(s); }
Then compile it, and observe the output. You know, printf-debugging™ is so cool! 🙂
Well, you’ll see that the program outputs “f(const void*)”. This means that the first function (the non-templated one, taking a const void*), is invoked.
So, why did the C++ compiler choose that overload? Why not f(const wchar_t*), synthesized from the second function template?
Well, the answer is in the rules that C++ compilers follow when doing template argument deduction. In particular, when deducing template arguments, the implicit conversions are not considered. So, in this case, the implicit CStringW conversion to const wchar_t* is not considered.
So, when overload resolution happens later, the only candidate available is f(const void*). Now, the implicit CStringW conversion to const wchar_t* is considered, and the first function is invoked.
Out of curiosity, if you comment out the first function, you’ll get a compiler error. MSVC complains with a message like this:
error C2672: ‘f’: no matching overloaded function found
error C2784: ‘void f(const CharT *)’: could not deduce template argument for ‘const CharT *’ from ‘ATL::CStringW’
The message is clear (almost…): “Could not deduce template argument for const CharT* from CStringW”: that’s because implicit conversions like this are not considered when deducing template arguments.
Well, what I’ve described above in a simplified case is basically what happens in the slightly more complex case of wcout.
wcout is an instance of wostream. wostream is declared in <iosfwd> as:
typedef basic_ostream<wchar_t, char_traits<wchar_t>> wostream;
Instead of the initial function f, in this case you have operator<<. In particular, here the candidates are an operator<< overload that is a member function of basic_ostream:
basic_ostream& basic_ostream::operator<<(const void *_Val)
and a template non-member function:
template<class _Elem, class _Traits> inline basic_ostream<_Elem, _Traits>& operator<<(basic_ostream<_Elem, _Traits>& _Ostr, const _Elem *_Val)
(This code is edited from the <ostream> standard header that comes with MSVC.)
When you write code like “wcout << s” (for a CStringW s), the implicit conversion from CStringW to const wchar_t* is not considered during template argument deduction. Then, overload resolution picks the basic_ostream::operator<<(const void*) member function (corresponding to the first f in the initial simplified case), so the string’s address is printed via this “const void*” overload (instead of the string itself).
On the other hand, when CStringW::GetString is explicitly invoked (as in “wcout << s.GetString()”), the compiler successfully deduces the template arguments for the non-member operator<< (deducing wchar_t for _Elem). And this operator<<(wostream&, const wchar_t*) prints the expected wchar_t string.
I know… There are aspects of C++ templates that are not easy.