简体   繁体   中英

static_cast - what does conversion between compatible types mean?

I understand that static_cast can convert between base and derived and between derived and base. dynamic_cast will check resulting object is a 'complete' object.

dynamic_cast uses RTTI feature. But how does static_cast work? What does 'compatible types' mean?

My question was really about what compatible types meant. But I am happy that have learnt something from posts. This sample demonstrates how the compiler interprets compatible types. Last few lines are most interesting. note interesting results.

#include <iostream>
#include <exception>
using namespace std;

class CBase { virtual void dummy() {} };
class CDerived: public CBase {  public: CDerived() : a(20) {} int a; };
class CDerived2: public CBase { public: CDerived2() : b(7) {} int b; };
class CDerived3: public CBase { public: CDerived3() : c('A') {} char c; };
class CNotDerived { int doit() const { return 9; } };

int main () {
  try {
      CBase * pba = new CDerived;
      CBase * pbb = new CBase;
      CDerived * pd;
      CNotDerived* pnot = new CNotDerived;
      CDerived2* pd2 = 0; 
      CDerived2* pdx = new CDerived2;
      CDerived3* pd3 = 0;


      pd = dynamic_cast<CDerived*>(pba);
      if (pd==0) cout << "Null pointer on first type-cast" << endl;   //ok

      pd = dynamic_cast<CDerived*>(pbb);
      if (pd==0) cout << "Null pointer on second type-cast" << endl;  //null ptr here

      pd = static_cast<CDerived*>(pbb);  //non-null pointer returned (not really what you want)
      if (pd==0) cout << "Null pointer on third type-cast" << endl;

// pd = dynamic_cast(pnot); //error C2683: 'dynamic_cast' : 'CNotDerived' is not a polymorphic type // if (pnot==0) cout << "Null pointer on fourth type-cast" << endl;

// pd = static_cast(pnot); //error C2440: 'static_cast' : cannot convert from 'CNotDerived *' to 'CDerived *' // if (pnot==0) cout << "Null pointer on fourth type-cast" << endl;

      //below lines compiled with ms vs2008 - I believe compiler SHOULD have flagged below as an error - but did not.
      pd2 = static_cast<CDerived2*>(pba); //compiles ok but obviously incorrect
      if (pd2==0) cout << "Null pointer on fourth type-cast" << endl;
      cout << pd2->b << endl;  //compiler had decided to give us CDerived->a value! Incorrect.

      pd2 = static_cast<CDerived2*>(pdx); //compiles ok 
      if (pd2==0) cout << "Null pointer on fourth type-cast" << endl;
      cout << pd2->b << endl;  //gives correct value for b (7)

      pd3 = static_cast<CDerived2*>(pdx); //error C2440: '=' : cannot convert from 'CDerived2 *' to 'CDerived3 *'
      if (pd3==0) cout << "Null pointer on fourth type-cast" << endl;
      cout << pd3->c << endl; 

  } catch (exception& e) {
      cout << "Exception: " << e.what();
  }
  return 0;
}

The main cases are they have a parent-child relationship or are both builtin numeric types. It's also valid if one object can be constructed from the other, integral types can be converted to enumerated types, and void pointers can be cast to pointer-to-object.

EDIT for the main cases (I omitted some more obscure cases like pointer-to-member casts):

5.2.9/2:

An expression e can be explicitly converted to a type T using a static_cast of the form static_cast(e) if the declaration “T t(e);” is wellformed, for some invented temporary variable t (8.5).

5.2.9/4:

Any expression can be explicitly converted to type “cv void.” The expression value is discarded.

5.2.9/5:

An lvalue of type “cv1 B”, where B is a class type, can be cast to type “reference to cv2 D”, where D is a class derived (clause 10) from B, if a valid standard conversion from “pointer to D” to “pointer to B” exists (4.10), cv2 is the same cvqualification as, or greater cvqualification than, cv1, and B is not a virtual base class of D.

7.2.9/7:

A value of integral type can be explicitly converted to an enumeration type.

7.2.9/10:

An rvalue of type “pointer to cv void” can be explicitly converted to a pointer to object type.

'Compatible types' means one of the following:

  1. Types that have a parent-child relationship
  2. Compatible built-in types (double to float, int to bool, etc)
  3. Types that have a defined conversion operator to some other otherwise unrelated type. For example, I might make a custom Fraction class and I can define operator double() for it, which will make my class convertible to a double.
  4. One type can be constructed from the other. For example, I can make a Fraction::Fraction(double) constructor, which will make a double convertible to my class.

Compared to dynamic_cast , static_cast could perform downcast on type that has no vtable while dynamic_cast cannot. static_cast will not check if the cast is valid at run time. It just adjusts pointer offset at compile time.

Compatible types means

  • native numeric types like double to long
  • base types and derived types
  • type that can cast to other types via cast operators

In case of static cast it is completely upto the programmer to decide if the casting will result in defined behaviour or undefined behaviour whereas in case of dynamic cast the check can be performed at runtime.

Thus the only difference between static cast and dynamic cast is that static cast does not require the run time overhead of that of dynamic cast.

In addition to the base - derived - base conversion, static_cast can also be used when an implicit or explicit conversion exists between two types.

Examples of implicit type conversion:

// From int to double
int i = 0;
double d = i;

class Integer
{
public:
    Integer(int value)
    :   value_(value)
    {
        // ...
    }

private:
    int value_;
};

// From int to Integer
int a = 0;
Integer b = a;

Examples of explicit type conversion:

// From double to int
double d = 0.0;
int i = static_cast<int>(d);

class Integer
{
public:
    explicit Integer(int value)
    :   value_(value)
    {
        // ...
    }

private:
    int value_;
};

// From int to Integer
int a = 0;
Integer b(a);
Integer c = static_cast<Integer>(a);

See: Type Casting

void foo( base *bPtr )
{
     derived *dPtr = static_cast< derived*> (bPtr) ; 
         // Safe as long as the bPtr has the derived class sub object too.
         // But what if bPtr is just pointing to a base* ?
}

derived* objOne = new derived() ;
foo( objOne ) ; // safe

base* obj = new base() ;
foo( obj ) ; // unsafe

In the function, it doesn't check the type compatibility and returns a derived* .

Demo: http://ideone.com/YwHJn

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM