In what way can C++0x standard help you eliminate 64-bit errors

28.02.2010 Andrey Karpov

Programmers see in C++0x standard an opportunity to use lambda-functions and other entities I do not quite understand :). But personally I see convenient means in it that allow us to get rid of many 64-bit errors.

Consider a function that returns "true" if at least one string contains the sequence "ABC".

typedef vector<string> ArrayOfStrings;
bool Find_Incorrect(const ArrayOfStrings &arrStr)
{
  ArrayOfStrings::const_iterator it;
  for (it = arrStr.begin(); it != arrStr.end(); ++it)
  {
    unsigned n = it->find("ABC");
    if (n != string::npos)
      return true;
  }
  return false;
};

This function is correct when compiling the Win32 version but fails when building the application in Win64. mode. Consider another example of using the function:

#ifdef IS_64
  const char WinXX[] = "Win64";
#else
  const char WinXX[] = "Win32";
#endif
int _tmain(int argc, _TCHAR* argv[])
{
  ArrayOfStrings array;
  array.push_back(string("123456"));
  array.push_back(string("QWERTY"));
  if (Find_Incorrect(array))
    printf("Find_Incorrect (%s): ERROR!\n", WinXX);
  else
    printf("Find_Incorrect (%s): OK!\n", WinXX);
  return 0;
}
Find_Incorrect (Win32): OK!
Find_Incorrect (Win64): ERROR!

The error here is related to choosing the type "unsigned" for "n" variable although the function find() returns the value of string::size_type type. In the 32-bit program, the types string::size_type and unsigned coincide and we get the correct result.

In the 64-bit program, these types do not coincide. As the substring is not found, the function find() returns the value string::npos that equals 0xFFFFFFFFFFFFFFFFui64. This value gets cut to 0xFFFFFFFFu and is written into the 32-bit variable. As a result, the condition 0xFFFFFFFFu == 0xFFFFFFFFFFFFFFFFui64 is always false and we get the message "Find_Incorrect (Win64): ERROR!".

We may correct the code using the type string::size_type.

bool Find_Correct(const ArrayOfStrings &arrStr)
{
  ArrayOfStrings::const_iterator it;
  for (it = arrStr.begin(); it != arrStr.end(); ++it)
  {
    string::size_type n = it->find("ABC");
    if (n != string::npos)
      return true;
  }
  return false;
};

Now the code works as it should though it is too long and not very nice to constantly add the type string::size_type. You may redefine it through typedef but still it looks somehow complicated. Using C++0x we can make the code much smarter and safer.

Let us use the key word "auto" to do that. Earlier, this word meant that the variable was created on the stack and it was implied if you had not specified something different, for example, register. Now the compiler identifies the type of a variable defined as "auto" on its own, relying on the function initializing this variable.

Note that an auto-variable cannot store values of different types during one instance of program execution. C++ remains a static typified language and "auto" only makes the compiler identify the type on its own: once the variable is initialized, its type cannot be changed.

Let us use the key word "auto" in our code. The project was created in Visual Studio 2005 while C++0x standard gets supported only beginning with Visual Studio 2010. So I chose Intel C++ compiler included into Intel Parallel Studio 11.1 and supporting C++0x standard to perform compilation. The option of enabling C++0x support is situated in the Language section and reads "Enable C++0x Support". As you may see in Figure 1, this option is Intel Specific.

Figure 1 - Support of C++0x standard

Figure 1 - Support of C++0x standard

The modified code looks as follows:

bool Find_Cpp0X(const ArrayOfStrings &arrStr)
{
  for (auto it = arrStr.begin(); it != arrStr.end(); ++it)
  {
    auto n = it->find("ABC");
    if (n != string::npos)
      return true;
  }
  return false;
};

Consider the way the variable "n" is defined now. Smart, isn't it? It also eliminates some errors including 64-bit ones. The variable "n" will have exactly the same type returned by the function find(), i.e. string::size_type. Note also that there is no string with the iterator definition:

ArrayOfStrings::const_iterator it;

It is not very smart to define the variable "it" inside the loop (for it is rather lengthy). So the definition was taken out of the loop. Now the code is short and accurate:

for (auto it = arrStr.begin(); ......)

Let us examine one more key word "decltype". It allows you to define the type of a variable relying on the type of another variable. If we had to define all the variables in our code beforehand, we could write it in this way:

bool Find_Cpp0X_2(const ArrayOfStrings &arrStr)
{
  decltype(arrStr.begin()) it;
  decltype(it->find("")) n;
  for (it = arrStr.begin(); it != arrStr.end(); ++it)
  {
    n = it->find("ABC");
    if (n != string::npos)
      return true;
  }
  return false;
};

Of course, it is senseless in our case but may be useful in some others.

Unfortunately (or fortunately for us :-), the new standard does not eliminate already existing defects in the code despite really simplifying the process of writing safe 64-bit code. To be able to fix an error with the help of memsize-тип or "auto" you must find this error at first. So, the tool Viva64 will not become less relevant with the appearance of standard C++0x.

P.S.
You may download the project with the code here.