1
2
3
short a=2000;
int b;
b=a;
a
is promoted from short
to int
without the need of any explicit operator. This is known as a standard conversion. Standard conversions affect fundamental data types, and allow the conversions between numerical types (short
to int
, int
to float
, double
to int
...), to or from bool
, and some pointer conversions.
Converting to int
from some smaller integer type, or to double
from float
is known as promotion, and is guaranteed to produce the exact same value in the destination type. Other conversions between arithmetic types may not always be able to represent the same value exactly:
-1
becomes the largest value representable by the type, -2
the second largest, ...).bool
consider false
equivalent to zero (for numeric types) and to null pointer (for pointer types); true
is equivalent to all other values and is converted to the equivalent of 1
.For non-fundamental types, arrays and functions implicitly convert to pointers, and pointers in general allow the following conversions:
void
pointers.const
or volatile
qualification.1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// implicit conversion of classes:
#include <iostream>
using namespace std;
class A {};
class B {
public:
// conversion from A (constructor):
B (const A& x) {}
// conversion from A (assignment):
B& operator= (const A& x) {return *this;}
// conversion to A (type-cast operator)
operator A() {return A();}
};
int main ()
{
A foo;
B bar = foo; // calls constructor
bar = foo; // calls assignment
foo = bar; // calls type-cast operator
return 0;
}
operator
keyword followed by the destination type and an empty set of parentheses. Notice that the return type is the destination type and thus is not specified before the operator
keyword.
B
, but it could as well be called with an object of type A
as argument:
explicit
keyword:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// explicit:
#include <iostream>
using namespace std;
class A {};
class B {
public:
explicit B (const A& x) {}
B& operator= (const A& x) {return *this;}
operator A() {return A();}
};
void fn (B x) {}
int main ()
{
A foo;
B bar (foo);
bar = foo;
foo = bar;
// fn (foo); // not allowed for explicit ctor.
fn (bar);
return 0;
}
explicit
cannot be called with the assignment-like syntax; In the above example, bar
could not have been constructed with:
explicit
. This prevents implicit conversions in the same way as explicit
-specified constructors do for the destination type.
1
2
3
4
double x = 10.3;
int y;
y = int (x); // functional notation
y = (int) x; // c-like cast notation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// class type-casting
#include <iostream>
using namespace std;
class Dummy {
double i,j;
};
class Addition {
int x,y;
public:
Addition (int a, int b) { x=a; y=b; }
int result() { return x+y;}
};
int main () {
Dummy d;
Addition * padd;
padd = (Addition*) &d;
cout << padd->result();
return 0;
}
Addition
, but then it assigns to it a reference to an object of another unrelated type using explicit type-casting:
result
will produce either a run-time error or some other unexpected results.
In order to control these types of conversions between classes, we have four specific casting operators: dynamic_cast
, reinterpret_cast
, static_cast
and const_cast
. Their format is to follow the new type enclosed between angle-brackets (<>
) and immediately after, the expression to be converted between parentheses.
dynamic_cast <new_type> (expression)
reinterpret_cast <new_type> (expression)
static_cast <new_type> (expression)
const_cast <new_type> (expression)
(new_type) expression
new_type (expression)
dynamic_cast
can only be used with pointers and references to classes (or with void*
). Its purpose is to ensure that the result of the type conversion points to a valid complete object of the destination pointer type.
This naturally includes pointer upcast (converting from pointer-to-derived to pointer-to-base), in the same way as allowed as an implicit conversion.
But dynamic_cast
can also downcast (convert from pointer-to-base to pointer-to-derived) polymorphic classes (those with virtual members) if -and only if- the pointed object is a valid complete object of the target type. For example:
// dynamic_cast
#include <iostream>
#include <exception>
using namespace std;
class Base { virtual void dummy() {} };
class Derived: public Base { int a; };
int main () {
try {
Base * pba = new Derived;
Base * pbb = new Base;
Derived * pd;
pd = dynamic_cast<Derived*>(pba);
if (pd==0) cout << "Null pointer on first type-cast.\n";
pd = dynamic_cast<Derived*>(pbb);
if (pd==0) cout << "Null pointer on second type-cast.\n";
} catch (exception& e) {cout << "Exception: " << e.what();}
return 0;
}
Null pointer on second type-cast.
dynamic_cast
requires Run-Time Type Information (RTTI) to keep track of dynamic types. Some compilers support this feature as an option which is disabled by default. This needs to be enabled for runtime type checking using dynamic_cast
to work properly with these types.
Base*
(pba
and pbb
) to a pointer object of type Derived*
, but only the first one is successful. Notice their respective initializations:
1
2
Base * pba = new Derived;
Base * pbb = new Base;
Base*
, pba
actually points to an object of type Derived
, while pbb
points to an object of type Base
. Therefore, when their respective type-casts are performed using dynamic_cast
, pba
is pointing to a full object of class Derived
, whereas pbb
is pointing to an object of class Base
, which is an incomplete object of class Derived
.
When dynamic_cast
cannot cast a pointer because it is not a complete object of the required class -as in the second conversion in the previous example- it returns a null pointer to indicate the failure. If dynamic_cast
is used to convert to a reference type and the conversion is not possible, an exception of type bad_cast
is thrown instead.
dynamic_cast
can also perform the other implicit casts allowed on pointers: casting null pointers between pointers types (even between unrelated classes), and casting any pointer of any type to a void*
pointer.
static_cast
can perform conversions between pointers to related classes, not only upcasts (from pointer-to-derived to pointer-to-base), but also downcasts (from pointer-to-base to pointer-to-derived). No checks are performed during runtime to guarantee that the object being converted is in fact a full object of the destination type. Therefore, it is up to the programmer to ensure that the conversion is safe. On the other side, it does not incur the overhead of the type-safety checks of dynamic_cast
.
1
2
3
4
class Base {};
class Derived: public Base {};
Base * a = new Base;
Derived * b = static_cast<Derived*>(a);
b
would point to an incomplete object of the class and could lead to runtime errors if dereferenced.
Therefore, static_cast
is able to perform with pointers to classes not only the conversions allowed implicitly, but also their opposite conversions.
static_cast
is also able to perform all conversions allowed implicitly (not only those with pointers to classes), and is also able to perform the opposite of these. It can:
void*
to any pointer type. In this case, it guarantees that if the void*
value was obtained by converting from that same pointer type, the resulting pointer value is the same.static_cast
can also perform the following:
enum class
values into integers or floating-point values.void
, evaluating and discarding the value.reinterpret_cast
converts any pointer type to any other pointer type, even of unrelated classes. The operation result is a simple binary copy of the value from one pointer to the other. All pointer conversions are allowed: neither the content pointed nor the pointer type itself is checked.
It can also cast pointers to or from integer types. The format in which this integer value represents a pointer is platform-specific. The only guarantee is that a pointer cast to an integer type large enough to fully contain it (such as intptr_t
), is guaranteed to be able to be cast back to a valid pointer.
The conversions that can be performed by reinterpret_cast
but not by static_cast
are low-level operations based on reinterpreting the binary representations of the types, which on most cases results in code which is system-specific, and thus non-portable. For example:
1
2
3
4
class A { /* ... */ };
class B { /* ... */ };
A * a = new A;
B * b = reinterpret_cast<B*>(a);
b
points to an object of a totally unrelated and likely incompatible class. Dereferencing b
is unsafe.
// const_cast
#include <iostream>
using namespace std;
void print (char * str)
{
cout << str << '\n';
}
int main () {
const char * c = "sample text";
print ( const_cast<char *> (c) );
return 0;
}
sample text
print
does not write to the pointed object. Note though, that removing the constness of a pointed object to actually write to it causes undefined behavior.
typeid
allows to check the type of an expression:
typeid (expression)
type_info
that is defined in the standard header <typeinfo>
. A value returned by typeid
can be compared with another value returned by typeid
using operators ==
and !=
or can serve to obtain a null-terminated character sequence representing the data type or class name by using its name()
member.
// typeid
#include <iostream>
#include <typeinfo>
using namespace std;
int main () {
int * a,b;
a=0; b=0;
if (typeid(a) != typeid(b))
{
cout << "a and b are of different types:\n";
cout << "a is: " << typeid(a).name() << '\n';
cout << "b is: " << typeid(b).name() << '\n';
}
return 0;
}
a and b are of different types: a is: int * b is: int
typeid
is applied to classes, typeid
uses the RTTI to keep track of the type of dynamic objects. When typeid
is applied to an expression whose type is a polymorphic class, the result is the type of the most derived complete object:
// typeid, polymorphic class
#include <iostream>
#include <typeinfo>
#include <exception>
using namespace std;
class Base { virtual void f(){} };
class Derived : public Base {};
int main () {
try {
Base* a = new Base;
Base* b = new Derived;
cout << "a is: " << typeid(a).name() << '\n';
cout << "b is: " << typeid(b).name() << '\n';
cout << "*a is: " << typeid(*a).name() << '\n';
cout << "*b is: " << typeid(*b).name() << '\n';
} catch (exception& e) { cout << "Exception: " << e.what() << '\n'; }
return 0;
}
a is: class Base * b is: class Base * *a is: class Base *b is: class Derived
name
of type_info
depends on the specific implementation of your compiler and library. It is not necessarily a simple string with its typical type name, like in the compiler used to produce this output.
Notice how the type that typeid
considers for pointers is the pointer type itself (both a
and b
are of type class Base *
). However, when typeid
is applied to objects (like *a
and *b
) typeid
yields their dynamic type (i.e. the type of their most derived complete object).
If the type typeid
evaluates is a pointer preceded by the dereference operator (*
), and this pointer has a null value, typeid
throws a bad_typeid
exception.
RetroSearch is an open source project built by @garambo | Open a GitHub Issue
Search and Browse the WWW like it's 1997 | Search results from DuckDuckGo
HTML:
3.2
| Encoding:
UTF-8
| Version:
0.7.4