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.

Monday, August 21, 2006

How to convert from std::string to CString?

This is simple... Use the CString constructor.
std::string strStdString ("Hello!");

// Using CString Constructor
CString strCString (strStdString.c_str ());

Let me add that CStringT can construct from both character or wide-character strings. i.e. It can convert from char* (i.e. LPSTR) or from wchar_t* (LPWSTR).

In other words, char-specialization (of CStringT) i.e. CStringA, wchar_t-specilization CStringW, and TCHAR-specialization CString can be constructed from either char or wide-character, null terminated (null-termination is very important here) string sources.

How to convert from CString to std::string?

There are really many ways to do it.

But, the simplest one is just this -
CString strSomeCstring ("This is a CString Object");

// Use ANSI variant CStringA to convert to char*; construct from it -
std::string strStdString (CStringA (strSomeCstring));

Note that as discussed in this post, CStringA is a template specialization of class CStringT for type char avaƃ­lable with Visual Studio 7.x and better.

Friday, April 28, 2006

Finding correlations between the CString Family and the Standard String Family

If we were to draw a correlation between the CString Family and Standard String Classes, we would notice -
  1. CStringT as the equivalent of std::basic_string
  2. CStringA as the equivalent of std::string
  3. CStringW as the equivalent of std::wstring
However, the standard library itself doesn't contain a "CString" equivalent - one that can at best be - std::basic_string <TCHAR>

This makes CString (VS 7.x +) "cool".

The history of CString...

CString really wasn't always as likeable as it is today. If one were to compare the CString of Visual Studio 7.x and better with the class supplied by MSVC 6.0, one might not be wrong in judging the erstwhile version as relatively inflexible, and restrictive.

What made CString of yesterday "inflexible"?

1.Only for MFC-folks!

Yes... CString with MSVC 6.0 was notoriously connected to MFC Projects as if other project types didn't have any need for a TCHAR String Wrapper Class! One reason for it would be the linker error one would get for using this class in a non-MFC Project.

2. Need to use a CString? Convert to TCHAR!

CString is a string wrapper for TCHAR strings. In doing so, it came with a good number of helpful member methods. However, the implementation of this class in MSVC 6.0 was such that if a programmer needed to make use of the utility member methods, he would need to convert his string data type to TCHAR - back and forth.

This "restriction" automatically excluded programmers using ANSI strings (char*) in UNICODE builds from using this class, and ditto for those using UNICODE strings (WCHAR*) in non-UNICODE builds from using this class - as in both these cases, the strings in question aren't TCHAR strings - the type that CString wraps.

What makes CString of today "flexible" and actually more "likeable"?

1. Use it in any project type - even in a console application!

Yes... All the programmer needs to do is to include the following header -
#include <atlstr.h>
2. Don't change your String Type to suit CString! Choose the right CString instead!

With Visual Studio 7.x and better, CString is actually a specialization of template Class CStringT for type T as TCHAR.

Additionally, there exist two useful specializations of CStringT -
  1. CStringA that specializes CStringT for type char, and
  2. CStringW that specilizes CStringT for type wchar_t.
Hence, CString is actually CStringA for a non-UNICODE build and is a CStringW for a UNICODE build.

The existence of CStringA and CStringW gives the programmer, the flexibility to use the utility member methods supplied by CStringT even when their strings aren't of type - TCHAR.

The rationale behind this FAQ...

In the past few years of answering questions aplenty on Codeguru's Visual C++ Forum, I have realized the following three things -

  1. CString counts amongst the most popularly used string classes - ever.
  2. People keep asking questions that have been answered many times over, but answers to which are not easily locatable.
  3. (1) and (2) are going to continue being this way for some time now.

So, me thinks - one centralized source of information on CString and related classes will help - someone, sometime!

Feel free to share feedback on the content you see.
Additionally, if you have a FAQ in mind that I haven't answered here - feel free to request one. ;)

Best Regards,
Siddhartha

___________________________
Siddhartha Rao
Microsoft MVP - Visual C++
Codeguru Community Moderator