Type Conversion
Type Coercion
- When an operator's operands are of different data types, C++ will automatically convert them to the same data type. The automatic conversion is known as type coercion.
- Promote, a value is converted to a higher data type
- Demote, a value is converted to a lower data type
- long double > double > float > unsigned long > long > unsigned int > int
- chars, shorts, and unsigned shorts are automatically promoted to int
- When an operator works with two values of different data type, the lower ranking value is promoted to the type of the higher ranking value
- When the final value of an expression is assigned to a variable, it will be converted to the data type of that variable
- If a negative integer value is converted to an unsigned type, the resulting value corresponds to its 2's complement bitwise representation
- The conversions from/to bool consider false equivalent to zero; true is equivalent to all other values and is converted to the equivalent of 1
- If the conversion is from a floating-point type to an integer type, the value is truncated (the decimal part is removed). If the result lies outside the range of representable values by the type, the conversion causes undefined behavior
- Otherwise, if the conversion is between numeric types of the same kind (integer-to-integer or floating-to-floating), the conversion is valid, but the value is implementation-specific (and may not be portable).
Implicit Conversion
#include <iostream>
#include <typeinfo>
int main(int argc, char *argv[])
{
//char
char c = 'a';
std::cout<<typeid(c+1).name()[0]<<std::endl;
//float and integer
float a = 10.5;
int b = 10;
std::cout<<typeid(a+b).name()[0]<<std::endl;
//negative integer to unsigned integer
unsigned int d = -1;
std::cout<<d<<std::endl;
//bool to integer
bool e = true, f = false;
std::cout<<"True: "<<e<<" False: "<<f<<std::endl;
//integer to bool
std::cout<<std::boolalpha<<"1: "<<bool(1)<<" 0: "<<bool(0)<<" -1: "<<bool(-1)<<std::endl;
std::cout<<std::noboolalpha;
//float to integer
float g = 3.14;
int h = g;
std::cout<<"Float to Int: "<<h<<std::endl;
return 0;
}
- Null pointers can be converted to pointers of any type
- Pointers to any type can be converted to void pointers
- Pointer upcast: pointers to a derived class can be converted to a pointer of an accessible and unambiguous base class, without modifying its const or volatile qualification.
Implicit Conversion with Classes
// implicit conversion of classes:
#include <iostream>
class A {};
class B {
public:
// conversion from A (constructor):
B (const A& x) {
std::cout<<"Constructor ..."<<std::endl;
}
// conversion from A (assignment):
B& operator= (const A& x) {
std::cout<<"Assignment ..."<<std::endl;
return *this;
}
// conversion to A (type-cast operator)
operator A() {
std::cout<<"Type-cast Operator ..."<<std::endl;
return A();
}
};
int main ()
{
A foo;
B bar = foo; // calls constructor
bar = foo; // calls assignment
foo = bar; // calls type-cast operator
return 0;
}
Explicit
//Vehicle.h
#ifndef VEHICLE_H
#define VEHICLE_H
#include <iostream>
#include <string>
class Vehicle
{
private:
std::string producer;
public:
//constructor
Vehicle(std::string s);
std::string getProducer() const {return producer;}
void setProducer(std::string producer);
std::string toString();
~Vehicle();
};
#endif
//Vehicle.cpp
#include <iostream>
#include <string>
#include "Vehicle.h"
//constructor
Vehicle::Vehicle(std::string s):producer(s)
{
std::cout<<"Vehicle Constructor ..."<<std::endl;
}
void Vehicle::setProducer(std::string producer)
{
this->producer = producer;
}
std::string Vehicle::toString()
{
std::string temp;
temp = "Vehicle Producer: "+producer;
return temp;
}
Vehicle::~Vehicle()
{
std::cout<<"Vehicle Destructor ..."<<std::endl;
}
//main.cpp
#include <iostream>
#include <string>
#include "Vehicle.h"
void display(Vehicle v)
{
std::cout<<v.toString()<<std::endl;
}
int main(int argc, char *argv[])
{
Vehicle v("Buick");
display(v);
std::string temp = "Honda";
display(temp);//due to implicit conversion, string will converted to Vehicle automatically
return 0;
}
Implicit conversions automatically convert string to Vehicle. However, it may lead unexpected results. To prevent the implicit conversion, use keyword explicit with constructor
#ifndef VEHICLE_H
#define VEHICLE_H
#include <iostream>
#include <string>
class Vehicle
{
private:
std::string producer;
public:
//constructor
explicit Vehicle(std::string s);
std::string getProducer() const {return producer;}
void setProducer(std::string producer);
std::string toString();
~Vehicle();
};
#endif
Type Cast of Built-int Data Types
#include <iostream>
int main(int argc, char *argv[])
{
double x = 3.14;
int temp;
temp = int(x);//C++ Style
temp = (int) x;//C Style
return 0;
}
const_cast
#include <iostream>
void display(int *number)
{
std::cout<<*number<<std::endl;
}
int main(int argc, char *argv[])
{
int i = 10;
const int *a = &i;
display(const_cast<int *>(a));
return 0;
}
dynamic_cast
//Vehicle.h
#ifndef VEHICLE_H
#define VEHICLE_H
#include <iostream>
#include <string>
class Vehicle
{
private:
std::string producer;
public:
//constructor
Vehicle() {}
Vehicle(std::string s);
std::string getProducer() const {return producer;}
void setProducer(std::string producer);
virtual std::string toString();
virtual ~Vehicle();
};
#endif
//Vehicle.cpp
#include <iostream>
#include <string>
#include "Vehicle.h"
//constructor
Vehicle::Vehicle(std::string s):producer(s)
{
std::cout<<"Vehicle Constructor ..."<<std::endl;
}
void Vehicle::setProducer(std::string producer)
{
this->producer = producer;
}
std::string Vehicle::toString()
{
std::string temp;
temp = "Vehicle Producer: "+producer;
return temp;
}
Vehicle::~Vehicle()
{
std::cout<<"Vehicle Destructor ..."<<std::endl;
}
//Car.h
#ifndef CAR_H
#define CAR_H
#include <iostream>
#include <string>
#include "Vehicle.h"
class Car : public Vehicle
{
private:
std::string model;
public:
//constructor
Car() {};
Car(std::string p, std::string m);
std::string getModel() const {return model;}
void setModel(std::string model);
std::string toString();
~Car();
};
#endif
//Car.h
#include <iostream>
#include <string>
#include "Vehicle.h"
#include "Car.h"
//constructor
Car::Car(std::string p, std::string m):Vehicle(p), model(m)
{
std::cout<<"Car Constructor ..."<<std::endl;
}
void Car::setModel(std::string model)
{
this->model = model;
}
std::string Car::toString()
{
std::string temp;
temp = "Car Producer: "+getProducer()+" Model: "+model;
return temp;
}
Car::~Car()
{
std::cout<<"Car Destructor ..."<<std::endl;
}
//main.cpp
#include <iostream>
#include <exception>
#include "Vehicle.h"
#include "Car.h"
int main(int argc, char *argv[])
{
std::exception ep;
Vehicle *v;
Car *c = new Car("Honda", "Accord");
//upcast
//Convert Car to Vehicle
std::cout<<"Upcast, Car to Vehicle ..."<<std::endl;
try
{
v = dynamic_cast<Vehicle *>(c);
std::cout<<v->toString()<<std::endl;
} catch (std::exception & e)
{
std::cout<<"Upcast Error: "<<e.what()<<std::endl;
}
delete v;
//delete c;
//downcast
Vehicle *v2 = new Vehicle("Buick");
Vehicle *v3 = new Car("Buick", "Rendezvous");
Car *c2;
//Convert Car to Car
std::cout<<"Downcast, Car to Car ..."<<std::endl;
try
{
c2 = dynamic_cast<Car*>(v3);
std::cout<<c2->toString()<<std::endl;
} catch (std::exception &e)
{
std::cout<<"Downcast Error, Car to Car: "<<e.what()<<std::endl;
}
//Convert Vehicle to Car
std::cout<<"Downcast, Vehicle to Car ..."<<std::endl;
try
{
c2 = dynamic_cast<Car*>(v2);
if (c2 == nullptr)
throw ep;
std::cout<<c2->toString()<<std::endl;
} catch (std::exception &e)
{
std::cout<<"Downcast Error, Vehicle to Car: "<<e.what()<<std::endl;
}
delete v2;
delete v3;
return 0;
}
// bad_cast example
#include <iostream> // std::cout
#include <typeinfo> // std::bad_cast
class Base {virtual void member(){}};
class Derived : Base {};
int main () {
try
{
Base b;
Derived& rd = dynamic_cast<Derived&>(b);
}
catch (std::bad_cast& bc)
{
std::cerr << "bad_cast caught: " << bc.what() << '\n';
}
return 0;
}
- dynamic_cast can only be used with pointers and reference of related classes, such as parent and child, require run time check to ensure that the converted pointers pointint to a valid object
- Upcast, same as an implicit conversion
- Downcast, need polymorphism
- Vehicle(Car) to Car, object type match
- Vehicle(Vehicle) to Car, object type does not match, return a null pointer, if dynamic_cast is used to convert to a reference type, a bad_cast is thrown
static_cast
#include <iostream>
#include <exception>
#include "Vehicle.h"
#include "Car.h"
int main(int argc, char *argv[])
{
std::exception ep;
Vehicle *v;
Car *c = new Car("Honda", "Accord");
//upcast
//Convert Car to Vehicle
std::cout<<"Upcast, Car to Vehicle ..."<<std::endl;
try
{
v = static_cast<Vehicle *>(c);
std::cout<<v->toString()<<std::endl;
} catch (std::exception & e)
{
std::cout<<"Upcast Error: "<<e.what()<<std::endl;
}
delete v;
//delete c;
//downcast
Vehicle *v2 = new Vehicle("Buick");
Vehicle *v3 = new Car("Buick", "Rendezvous");
Car *c2;
//Convert Car to Car
std::cout<<"Downcast, Car to Car ..."<<std::endl;
try
{
c2 = static_cast<Car*>(v3);
std::cout<<c2->toString()<<std::endl;
} catch (std::exception &e)
{
std::cout<<"Downcast Error, Car to Car: "<<e.what()<<std::endl;
}
//Convert Vehicle to Car
std::cout<<"Downcast, Vehicle to Car ..."<<std::endl;
try
{
c2 = static_cast<Car*>(v2);
if (c2 == nullptr)
throw ep;
std::cout<<c2->toString()<<std::endl;
} catch (std::exception &e)
{
std::cout<<"Downcast Error, Vehicle to Car: "<<e.what()<<std::endl;
}
delete v2;
delete v3;
return 0;
}
#include <iostream>
int main(int argc, char *argv[])
{
//convert integer to float
float temp = static_cast<float>(9)/5;
std::cout<<temp<<std::endl;
//convert integer to enum
enum season {spring, summer, autumn, winter};
season s = static_cast<season>(3);
//convert *void to any pointer type
char *a;
unsigned char *b;
//b = static_cast<unsigned char *>(a);//error
b = static_cast<unsigned char *>(static_cast<void*>(a));
//b = reinterpret_cast<unsigned char *>(a);
return 0;
}
- Being used to cast between the integer types, such as char->long, int->short etc.
- Is also used to cast pointers to related types, no runtime checking to guarantee the object being converted to the destination type, programmer should ensure that the conversion is safe
- For casting to and from void*, static_cast should be preferred
- Upcast, same as an implicit conversion
- Downcast
- Vehicle(Car) to Car, object type match
- Vehicle(Vehicle) to Car, object type match, no error is thrown
reinterpret_cast
#include <iostream>
#include "Vehicle.h"
#include "Car.h"
int main(int argc, char *argv[])
{
//convert *void to any pointer type
char *a = new char();
a[0] = 'A';
unsigned char *b;
b = reinterpret_cast<unsigned char *>(a);
std::cout<<b<<std::endl;
delete a;
//convert Car to Car
Vehicle *v = new Car("Honda", "Accord");
Car *c;
c = reinterpret_cast<Car *>(v);
std::cout<<c->toString()<<std::endl;
delete v;
//convert Vehicle to Car
Vehicle *v2 = new Vehicle("Buick");
Car *c2;
c2 = reinterpret_cast<Car *>(v2);
std::cout<<c2->toString()<<std::endl;
delete v2;
return 0;
}
- Converts any pointer type to any other pointer type, even of unrelated classes