Suppose that you have a C++ code base containing methods and functions that take ATL/MFC CString objects as input parameters, for example:
void DoSomething(const CString& s)
and you want to introduce the use of std::string_view in that code.
Let’s assume that your code base is built using Visual Studio in Unicode mode (Configuration Properties | Advanced | Character Set set to Use Unicode Character Set), which has been the default since Visual Studio 2005. In this case, CString is actually the wchar_t-based CStringW, and the matching std::basic_string_view is the wchar_t-based std::wstring_view.
(Note: It could be possible to convert between Unicode UTF-16 CStringW and UTF-8, and store the UTF-8 encoded text in a std::string object, and take a std::string_view on that, but let’s not make things even more complicated!)
So, you may think of adding a wstring_view overload like this:
void DoSomething(std::wstring_view s)
Unfortunately, that would cause a problem with your pre-existing perfectly compiling code. In fact, if you pass a string literal, like this:
DoSomething(L"Connie");
the C++ compiler now has two options for DoSomething: the initial form that takes a const CString&, and the new one that takes a wstring_view. The C++ compiler has no way to choose which overload to pick, so it emits an error complaining about the ambiguous call to the DoSomething overloaded function.
At this point, you may think of removing the previous CString overload, and only keep the new wstring_view version:
// Removed: void DoSomething(const CString&) void DoSomething(std::wstring_view s)
OK, now the C++ compiler is happy as there is only one function to pick.
So, problem solved? Nope. Don’t forget that this is C++! 😊 And things can get more “interesting” when you apparently solved one problem.
For example, in your existing code base you almost certainly have a number CString objects around. For the sake of simplicity, consider this code snippet:
CString name; DoSomething(name);
This code worked perfectly fine before you added wstring_view, but now the compiler complains! The problem this time is that the C++ compiler cannot find a suitable way to convert from CString to wstring_view. How can you fix that?
Well, you can invoke the CString::GetString method. In fact, this method returns a const wchar_t* raw pointer to the NUL-terminated C-style string associated with the CString object. And wstring_view has a constructor overload that matches this particular case, constructing a view of the NUL-terminated character string passed as input pointer parameter.
This will work:
// name is a CString DoSomething(name.GetString());
Another important consideration to make is that wstring_view is just a view to a string, so you must pay attention that the pointed-to string is valid for at least all the time you are referencing it via the wstring_view. In other words, pay attention to dangling references and string views that refer to strings that have been deallocated or moved elsewhere in memory.
Warning: converting CString to string_view in this way costs O(string length) because of the implicit call to strlen.
You can avoid the extra cost with:
DoSomething(std::wstring_view(name.GetString(), name.GetLength()));
But this becomes quite inconvenient, especially when ‘name’ is a long expression. I’d rather write a conversion function
std::wstring_view as_view(const CString& s) {
return {s.GetString(), size_t(s.GetLength())};
}
and replace the calls with DoSomething(as_view(name));
Good point. I discussed that in details in another blog post. Just a couple of notes that 1) in the case discussed here wcslen is invoked instead of strlen to get the string length, and 2) I prefer using static_cast instead of the cast syntax you used.