Previous section   Next section

Imperfect C++ Practical Solutions for Real-Life Programming
By Matthew Wilson
Table of Contents
Chapter 19.  Casts


19.3. The Case for C Casts

Despite all that we read about C-style casting, there are still circumstances where they are useful, but it's a very few: laziness and sloppiness don't count. There are only two legitimate uses that I can think of.

The first one is where the code has to be compilable under both C and C++, and consequently the only available cast operator is C's. This may be because it is in a macro, or is in a source file that is introduced via inclusion, either as an inline function (see Chapter 12) or one with internal linkage (see sections 6.4 and 11.1).

In actual fact, since we should prefer to go for more power whenever it is available, the Synesis libraries have a set of cast macros—SyCastStatic, SyCastConst, SyCastVolatile, SyCastDynamic, SyCastRaw, and SyCastC—that are plain-old C casts in C, and the appropriate C++ cast in C++. It's ugly, but it works, and this approach has caught countless inappropriate casts.

The second use is subtler. Because the C++ casts are particular about the relationships between their operand and target types, one can get into scrapes in template code with them. For example, despite being The Thing in our "Fancastic Four," reinterpret_cast will refuse to cast when a const (or volatile) change is required as part of the conversion. The following code illustrates:

Listing 19.2.


template< typename T1


        , typename T2


        >


struct test_cast


{


  test_cast(T2 *p2)


  {


    m_p1 = static_cast<T1*>(p2);      // a


    m_p1 = reinterpret_cast<T1*>(p2); // b


    m_p1 = const_cast<T1*>(p2);       // c


    m_p1 = (T1*)p2;                   // d


  }


  operator T1 *()


  {


    return m_p1;


  }


// Members


protected:


  T1  *m_p1;


};





int main()


{


  test_cast<int , int >       tc1(NULL);  // 1


  test_cast<int , short >     tc2(NULL);  // 2


  test_cast<int , int const > tc3(NULL);  // 3





  return 0;


}



Of the first scenario (casting int* to int*), all four casts will work. Of the second (casting int* to short*), only reinterpret_cast and the C-style cast work. Of the third (casting int* to int const*), only const_cast and the C-style cast work. While it is possible in some circumstances to deduce the respective const-ness of the two types, and thereby cause the correct permutation of const_cast and reinterpret_cast to be invoked, this requires a degree of template sophistication lacking in some compilers. The portable answer (if a cast can ever be considered portable!) may therefore be to use a C-style cast. (Naturally, I am assuming I'm talking to an audience that is fully aware of the badness of C-style casts, and eschews their use as a matter of course—it takes technical maturity and a steely nerve to use C-style casts—but I believe in being aware of all ramifications before adhering to rules, even very good ones.)

Outside these circumstances, the use of C-style casts should be, avoided. The practical problem is that there is so much code containing them, and removal is very difficult because they just blend into the scenery; it is no accident that the C++ operators are so distinct, some say ugly [Stro1994]. Thankfully some compilers provide a little help in this regard. GCC provides the compiler option –Wold-style-cast to provide warnings at each C cast. Recent versions of the Digital Mars and Comeau compilers provide the same functionality with the -wc and –C_style_cast_warning options, respectively.[3]

[3] Both these vendors were kind enough to respond to my requests for these features in impressively short times. How's that for service?

Specifying the requisite flag to the Comeau (version 4.3.3 or later), Digital Mars (version 8.29 or later), or GCC compilers results in your receiving a warning for each and every C-style cast—another reason to use multiple compilers in your work.


      Previous section   Next section