Sunday, November 19, 2006

What's the deal with CString and explicit constructors (C2440)?

Let me first address the question:

Q. What are explicit constructors?
A. Constructors that use the explicit keyword. Explicit constructors are used to avoid unintentional conversions. Constructors that don't use the C++ explicit keyword are implicit by default.

Q. Why are they used by CString?
A. CString is functionally a string class designed to make working with TCHAR strings (TCHAR* a.k.a LPTSTR) easier. LPTSTRs as we know function as char* for non-UNICODE builds and as WCHAR* for UNICODE builds.

As CString supplies the flexibility to construct from a non-UNICODE String (i.e. char* a.k.a LPSTR) or from a UNICODE string (i.e. WCHAR* a.k.a. LPWSTR) for either build scenarios - effectively facilitating for conversion of all types to LPTSTR, it becomes important to avoid accidental conversions, but to allow intentional ones.

Starting Visual C++ 7.x, the new templatized version of the CString class (CStringT) is shipped with constructors that are declared as explicit. Depending on your programming style, this may result in you getting an error when implicitly constructing a CString object from a non-TCHAR string type.

The error will typically look like this:
error C2440: 'initializing' : cannot convert from 'const char [4]' to 'ATL::CStringT<BaseType,StringTraits>'
Q. What do you mean "implicitly converting a CString object from a non-TCHAR string"?
A. It means this -

// Implicit conversion of a UNICODE string in a non-UNICODE build that results in C2440:
CString strUnicode = L"This is a UNICODE string";

Or, this -
// Implicit conversion of a non-UNICODE string in a UNICODE build that results in C2440:
CString strNonUnicode = "This is an ANSI non-UNICODE string";

Q. Okay, how can I fix this issue?
A. There are many solutions to this problem.

Solution # 1:
Avoid implicit conversions:
CString strCStringObject ("This is a non-UNICODE string");
CString strAnotherCStringObject (L"This is a UNICODE string");

...or better still:
CString strJustAnother (_T ("This is a TCHAR string a.k.a. LPCTSTR"));
Solution # 2 (for those that still want to use '=' at the construction step):

Assign a TCHAR compliant string.
CString strCStringObject = CString ("A non-UNICODE string");
CString strAnotherCStringObject = CString (L"A UNICODE string");

...Or even:
CString strConstructAssign = _T ("This is a TCHAR String a.k.a LPCTSTR");

Solution # 3
(for those that wish to disable CString explicit constructors):

To switch explicit constructors off, comment the macro _ATL_CSTRING_EXPLICIT_CONSTRUCTORS defined typically in your project's stdafx.h.

Note that commenting this macro out will keep the explicit keywords used with CStringT from being compiled, and will rid you of your errors too - but you will also not be warned of implicit, unintentional conversions.

I would recommend either of the first two solutions above.