C++ primitive types like
char
,short
, orint
are the heritage of primitive types in the C programming language. C++ adds a short list of its own primitive types likebool
plus a long list of non-primitive types likestd::string
; it is the possibility of introducing new types based on, mostly, theclass
construct/keyword.
Rule(s)
- In C++, primitive types are (
bool
withfalse
andtrue
as possible values),char
,wchar_t
,char16_t
,char32_t
,short
(at least 16-bit long),int
(and variants),long
(at least 32-bit long),long long
(at least 64-bit long),float
,double
andlong double
.signed
(default) andunsigned
may be used as modifiers forchar
,short
,int
,long
and,long long
. Theunsigned
modifier ignores sign bit.void
is a special type in the sense thatvoid variable;
is forbidden whilevoid* pointer;
is allowed.- Other common (non-primitive) type is the string type:
char*
andstd::string
(with#include <string>
).- Depending upon computer architecture and Operating System (OS), the size of a C++ type or variable (in bytes) may be computed with the help of the
sizeof
operator. Type variation intervals are available through predefined constants. For example, [SHRT_MIN
SHRT_MAX
] is the variation interval ofshort
.Example
assert(sizeof(long) == 8); // 64-bit Windows OS // Access to 'SHRT_MAX' or 'SHRT_MIN' requires '#include <climits>' std::cout << "short: " << sizeof (short) << '\n'; // '2' is displayed std::cout << '\n' << "'SHRT_MAX': " << SHRT_MAX << '\n' // '32767' is displayed << "'SHRT_MIN': " << SHRT_MIN << '\n'; // '-32768' is displayed
Rule(s)
char
variants require specific management with value prefixes that characterize types (note that the same applies forstd::string
variants likestd::wstring
).Example
char16_t tuo = u'拖'; // Mandarin char32_t taa = U'她'; // Cantonese
Example
class Currency { public: // 'std::wstring' <=> 'std::basic_string<wchar_t>': const std::wstring common_symbol; // L"$" <- american dollar const int iso_code; // 840 <- american dollar const std::wstring iso_symbol; // L"USD" <- american dollar // 'wchar_t' is 2-byte long: const wchar_t short_symbol; // L'$' protected: Currency(const std::wstring&, int, std::wstring, wchar_t = L'$'); … }
See also
Numbers representation
Rule(s)
- Most programming languages require a way of representing primitive types (numbers especially) in memory. Compilers crucially rely on type representation to instrument calculations: +, -, *, /, …
- C++ does not impose a unique representation for numbers (integers and floating point numbers) even though the IEEE 754 standard is often used for floating point numbers.
Example (short integers) Numbers.Cpp.zip
#include <bitset> #include <climits> // 'CHAR_BIT', SHRT_MAX'... … std::bitset<sizeof (short) * CHAR_BIT > one(1); // How many bits in a byte? -> 'CHAR_BIT == 8' std::cout << '\n' << "'one': " << one << '\n'; // '0000 0000 0000 0001' // Bit positions that have been vacated by a shift operation are zero-filled: std::bitset<sizeof (short) * CHAR_BIT > two(1 << 1 /* 1-bit left shift */); std::cout << "'two': " << two << '\n'; // '0000 0000 0000 0010' std::bitset<sizeof (short) * CHAR_BIT > zero(1 >> 1 /* 1-bit right shift */); std::cout << "'zero': " << zero << '\n'; // '0000 0000 0000 0000' std::bitset<sizeof (short) * CHAR_BIT > minus_one(-1); std::cout << "'minus_one': " << minus_one << '\n'; // '1111 1111 1111 1111' // How negative numbers are represented (www.devx.com/tips/Tip/16134): std::bitset<sizeof (short) * CHAR_BIT > minus_minus_one(~minus_one); // ~'1111 1111 1111 1111' -> '0000 0000 0000 0000' minus_minus_one[0] = true; // '0000 0000 0000 0001' std::cout << "'minus_minus_one': " << minus_minus_one << '\n'; // '0000 0000 0000 0001' // If you left-shift a signed number so that the sign bit is affected, the result is undefined: std::bitset<sizeof (short) * CHAR_BIT > unknown(16384 << 1); // '0100 0000 0000 0000' << 1 std::cout << "'unknown': " << unknown << '\n'; // '1000 0000 0000 0000' std::bitset<sizeof (short) * CHAR_BIT > short_min(SHRT_MIN); // '-32768' std::cout << "'short_min': " << short_min << '\n'; // '1000 0000 0000 0000' std::cout << '\n' << "'SHRT_MAX': " << SHRT_MAX << '\n' // '0111 1111 1111 1111' / 32767 << "'SHRT_MIN': " << SHRT_MIN << '\n'; // '1000 0000 0000 0000' / -32768 assert(SHRT_MAX != -SHRT_MIN); assert(SHRT_MAX == ~SHRT_MIN);
Example (floating point numbers) Numbers.Cpp.zip
#include <climits> // 'CHAR_BIT'... #include <cmath> // 'pow'... … /** * Floating point numbers (www.learncpp.com/cpp-tutorial/25-floating-point-numbers/) * IEEE 754 standard (www3.ntu.edu.sg/home/ehchua/programming/java/datarepresentation.html) */ union { float f = 1.1F; // '0 - 0111 1111 - 000 1100 1100 1100 1100 1101' // -> 1 sign bit + 8 exponent bits + 23 fraction (significant) bits = 32 bits uint32_t i; // if 'sizeof (float) == 8' then 'uint64_t' } _one_point_one; // S == '0' -> positive // E == '0111 1111' -> 127 // Fraction == '000 1100 1100 1100 1100 1101' -> in IEEE 754 standard, the actual fraction is normalized with an implicit leading '1', so // the actual fraction is 1.000 1100 1100 1100 1100 1101 = 1 + 2^-4 + 2^-5 + 2^-8 + 2^-9 + 2^-12 + 2^-13 + // 2^-16 + 2^-17 + 2^-20 + 2^-21 + 2^-23 = 1 + 1/16 + 1/32 + ... = 1.1 // About E: in IEEE 754 standard, the actual exponent is 'E - 127' (so-called excess-127 or bias-127). // This is because we need to represent both positive and negative exponent. // With an 8-bit E, ranging from 0 to 255, the excess-127 scheme could provide actual exponent of -127 to 128. /** * Final result: * 1.1 * 2^(E - 127) = 1.1 * 2^0 = 1.1 */ float check = 1 + pow(2.F, -4.F) + pow(2.F, -5.F) + pow(2.F, -8.F) + pow(2.F, -9.F) + pow(2.F, -12.F) + pow(2.F, -13.F) + pow(2.F, -16.F) + pow(2.F, -17.F) + pow(2.F, -20.F) + pow(2.F, -21.F) + pow(2.F, -23.F); std::bitset<sizeof (uint32_t) * CHAR_BIT > one_point_one(_one_point_one.i); std::cout << '\n' << "'one_point_one': " << one_point_one << '\n' // '0000 0000 0000 0001' << "'check': " << check << '\n';
Numerical stability
Rule(s)
- From C++11, the numerical stability of calculations may be controlled on a fine-grain basis. On purpose, constants and functions are provided.
Example Numbers.Cpp.zip
#include <cfloat> // 'DBL_MAX'... #include <cmath> // 'isinf', 'INFINITY'... … std::cout << '\n' << std::boolalpha << "std::isinf(1 / 0): " << std::isinf(1 / 0) << '\n'; // 'false' because division by zero is undefined here... std::cout << '\n' << std::boolalpha << "std::isnan(NAN): " << std::isnan(NAN) << '\n' // 'true' << "std::isnan(INFINITY): " << std::isnan(INFINITY) << '\n' // 'false' << "std::isnan(1 / 0.F): " << std::isnan(1 / 0.F) << '\n' // 'false' << "std::isinf(1 / 0.F): " << std::isinf(1 / 0.F) << '\n' // 'true' << "std::isnan(DBL_MAX * 2.): " << std::isnan(DBL_MAX * 2.) << '\n' // 'false' << "std::isinf(DBL_MAX * 2.): " << std::isinf(DBL_MAX * 2.) << '\n' // 'true' << "std::isnan(0 / 0.): " << std::isnan(0 / 0.) << '\n' // 'true' << "std::isnan(INFINITY - INFINITY): " << std::isnan(INFINITY - INFINITY) << '\n'; // 'true'
Rule(s)
- C++11 comes with a lot of non-primitive types. A key enhancement is embodied by the
<chrono>
header file that contains time-related types beyond the old-fashion types in the historical<ctime>
header file.Predefined types: the case of
<chrono>
Example (old school)
std::time_t now = std::time(&now); // '#include <ctime>' std::tm *now_as_data_structure = std::gmtime(&now); int year = now_as_data_structure->tm_year + 1900; // Caution: 'tm_year' returns the current year minus '1900'!
Rule(s)
<chrono>
key types arestd::chrono::duration
(with instantiations:std::chrono::nanoseconds
,std::chrono::microseconds
…),std::chrono::time_point
,std::chrono::system_clock
… with direct interoperability withstd::time_t
andstd::tm
in<ctime>
.Example
std::chrono::seconds my_duration(5); using namespace std::chrono_literals; // '<chrono>' reserved suffixes... std::chrono::milliseconds my_duration_ = 5000ms; std::this_thread::sleep_for(5s); // Wait for 5s...
Example (elapsed time measurement)
std::chrono::time_point now = std::chrono::high_resolution_clock::now(); // System clock... // Something, which lasts, to be measured... // 'std::chrono::milliseconds' 1s / 1 000 // 'std::chrono::microseconds' 1s / 1 000 000 // 'std::chrono::nanoseconds' 1s / 1 000 000 000 std::chrono::duration duration = std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::high_resolution_clock::now() - now); std::cout << "Elapsed time: " << duration.count() << std::endl << std::endl;
Predefined types: the case of
std::filesystem::path
(C++17)Rule(s)
<filesystem>
comes with simplified file manipulation (get file properties as prefix, suffix, size…), which hides the underlying Operating System -OS-.Example (working directory of running process)
std::cout << "Path of working directory: " << std::filesystem::current_path() << std::endl;
Example
std::filesystem::path path("/"); // UNIX style adapts to Windows, i.e., "C:\\" path /= "Users"; path /= "franc"; path /= "Desktop"; path /= "Franck.jpg"; std::cout << "exists(): " << std::boolalpha << std::filesystem::exists(path) << "\n" // 'true' << "root_name(): " << path.root_name() << "\n" // "" << "root_path(): " << path.root_path() << "\n" // "/" << "relative_path(): " << path.relative_path() << "\n" // "Users\\franc\\Desktop\\Franck.jpg" << "parent_path(): " << path.parent_path() << "\n" // "/Users\\franc\\Desktop" << "filename(): " << path.filename() << "\n" // "Franck.jpg" << "stem(): " << path.stem() << "\n" // "Franck" << "extension(): " << path.extension() << "\n"; // ".jpg" if (std::filesystem::exists(path) && std::filesystem::is_regular_file(path)) { std::error_code error_code; std::uintmax_t size = std::filesystem::file_size(path, error_code); if (size != static_cast<std::uintmax_t>(-1)) std::cout << "size: " << size << "\n"; // '6176' else std::cout << "size (error): " << error_code.category().message(error_code.value()) << "\n"; }
See also
- C++17 file system API here…
Data structures
Rule(s)
- User-defined types like data structures are constructed by means of the
struct
,union
orclass
keywords. Onlyclass
should be used sincestruct
andunion
are old-fashion C style: properties insidestruct
-based andunion
-based data structures are nothing else thanpublic:
.Example (
.h
header file) Numbers.Cpp.zipstruct My_data_structure { // All properties are by definition 'public:' bool bool_attribute; char char_attribute; unsigned char unsigned_char_attribute; short int short_attribute; // <=> 'short' or 'signed short int' unsigned short int unsigned_short_attribute; // <=> 'unsigned short' int int_attribute; // <=> 'signed int' unsigned int unsigned_int_attribute; long int long_attribute; // <=> 'long' or 'signed long int' unsigned long int unsigned_long_attribute; // <=> 'unsigned long' long long int long_long_attribute; // <=> 'long long' or 'signed long long int' unsigned long long int unsigned_long_long_attribute; // <=> 'unsigned long long' float float_attribute; double double_attribute; long double long_double_attribute; struct { // All properties are by definition 'public:' double first; double second; } struct_attribute; union { // All properties are by definition 'public:' char is_char; int is_int; } union_attribute; };
Example (
.cpp
header file) Numbers.Cpp.zipMy_data_structure object; std::cout << "bool: " << sizeof (bool) << '\n'; std::cout << "char: " << sizeof (object.char_attribute) << '\n'; std::cout << "short: " << sizeof (short) << '\n'; std::cout << "int: " << sizeof (int) << '\n'; std::cout << "long: " << sizeof (long) << '\n'; std::cout << "long long: " << sizeof (long long) << '\n'; std::cout << "float: " << sizeof (float) << '\n'; std::cout << "double: " << sizeof (double) << '\n'; std::cout << "long double: " << sizeof (long double) << '\n'; std::cout << "My_data_structure: " << sizeof (My_data_structure) << "\n\n"; object.bool_attribute = true; std::cout << object.bool_attribute << '\n'; object.char_attribute = '\r'; std::cout << object.char_attribute << '\n'; object.unsigned_char_attribute = 'a'; std::cout << object.unsigned_char_attribute << '\n'; object.int_attribute = (short) 0xFFFF; std::cout << object.int_attribute << '\n'; object.unsigned_short_attribute = 0xFFFF; std::cout << object.unsigned_short_attribute << '\n'; object.int_attribute = (int) 6.5E1; std::cout << object.int_attribute << '\n'; object.unsigned_int_attribute = (unsigned int) 7.2e-2; std::cout << object.unsigned_int_attribute << '\n'; object.long_attribute = 500000L; std::cout << object.long_attribute << '\n'; object.unsigned_long_attribute = 0xFFFFFFFE + 1L; std::cout << object.unsigned_long_attribute << '\n'; object.long_long_attribute = 9LL; std::cout << object.long_long_attribute << '\n'; object.unsigned_long_long_attribute = 0xFFFFFFFFFFFFFFFE + 1LL; std::cout << object.unsigned_long_long_attribute << '\n'; object.float_attribute = 1.F; std::cout << object.float_attribute << '\n'; object.double_attribute = 1.; std::cout << object.double_attribute << '\n'; object.long_double_attribute = 1.L; std::cout << object.long_double_attribute << '\n'; object.union_attribute.is_char = 'A'; std::cout << "'A' as 'char': " << object.union_attribute.is_char << '\n'; std::cout << "'A' as 'int': " << object.union_attribute.is_int << '\n'; object.union_attribute.is_int = 66; std::cout << "'66' as 'char': " << object.union_attribute.is_char << '\n'; std::cout << "'66' as 'int': " << object.union_attribute.is_int << '\n';
Aggregate initialization
Rule(s)
- From C++11, curly braces may instrument object initialization in a straightforward way. Curly brace-based initialization relies on constructors in an implicit way or on
std::initializer_list
in an explicit way.Example (implicit) Numbers.Cpp.zip
std::bitset<CHAR_BIT> a{97}; // 'a' std::byte a_{97}; // 'a' // C++17: std::cout << std::boolalpha << "'std::is_aggregate_v<My_data_structure>': " << std::is_aggregate_v<My_data_structure> << std::endl; // 'true' // C++11 (included: bool, ..., long long) (excluded: enum, float...) : std::cout << std::boolalpha << "'std::is_integral<decltype(object.char_attribute)>': " << std::is_integral_v<decltype(object.char_attribute)> << std::endl; // 'true' // C++14: My_data_structure another_object{false, 'a', 'a', 1, 1, 1, 1, 1L, 1L, 1LL, 1LL, 1.F, 1., 1.L, {1., 1.}}; // C++20 (designated initialization): My_data_structure yet_another_object{.bool_attribute = false /*,.char_attribute = 'a'*/, .unsigned_char_attribute = 'b'}; std::string FB[] = {"Franck", /* list initialization -> */ {'B', 'a', 'r', 'b', 'i', 'e', 'r'}}; wchar_t F[] = L"Franck";
Example (explicit) Lambda_expression_.Cpp.zip
'.h' file: class Election { std::string _separator; std::unordered_multimap<std::string, std::string> _situation; public: Election(const std::string&, const std::initializer_list<std::unordered_multimap<std::string, std::string>::value_type>&); … }; '.cpp' file: Election::Election(const std::string& separator, const std::initializer_list<std::unordered_multimap<std::string, std::string>::value_type>& situation) { _separator = separator; for (std::unordered_multimap<std::string, std::string>::value_type pair : situation) // "Range-based for" _situation.insert(pair); } … Election fr_2022_presidential_election{ " - ", { {"Macron", Election::President}, {"Macron", Election::Candidate}, {"Castex", Election::Prime_minister}, {"Mélanchon", Election::Challenger}, {"Mélanchon", Election::Candidate}, {"Le Pen", Election::Challenger}, {"Le Pen", Election::Candidate} }};
User-defined suffixes
Rule(s)
- From C++11, literals (integers, characters, floating-point numbers, strings, booleans and pointers) may be suffixed to reinforce the type of a literal towards an existing (see
<chrono>
above) or user-defined type.Example (existing versus user-defined literal suffix) Inline_namespace.Cpp.zip
'.h' file: namespace New_York_City_Penitentiary { class Prisoner { const std::string _prison_file_number; … public: explicit Prisoner(std::string&&); … }; … // Literal suffix (re)-definition cannot be a member function: Prisoner operator ""_Prisoner(const char[], size_t); // '_' is required not to conflict with hypothetical (future) use of 'Prisoner' as suffix... } '.cpp' file: New_York_City_Penitentiary::Prisoner::Prisoner(std::string&& prison_file_number) : _prison_file_number(prison_file_number) { } New_York_City_Penitentiary::Prisoner New_York_City_Penitentiary::operator ""_Prisoner(const char prison_file_number[], size_t size) { std::string _prison_file_number(prison_file_number); return New_York_City_Penitentiary::Prisoner(std::move(_prison_file_number)); } … using namespace New_York_City_Penitentiary; using namespace std::literals::string_literals; //// Operator""s is made visible... // Prisoner fb = "1630125388055-29"s; // Compilation error: 'explicit' constructor precludes implicit conversion... Prisoner fb = "1630125388055-29"_Prisoner;
See also
Rule(s)
- C++ arrays are managed as common pointers. However, C++ increases type checking at compilation time that prevents potential errors at run-time.
Example
char* given_name = {'F','r','a','n','c','k'}; // Old style, compilation error in "modern" C++! char given_name[] = {'f','r','a','n','c','k'}; // Better! given_name[0] = 'F';
Rule(s)
- Beyond initialization time, C++ arrays may grow (or shrink) on demand.
Example
T tab[1000]; // Fixed capacity // Alternatively: T tab[]; // Variable capacity tab = new T[1000] // Allocation of memory delete[] tab; // Full desallocation of memory while 'delete[500] tab;' is a partial desallocation because '500' < '1000'
Rule(s)
- Enumerated types raise the problem of matching to
int
. Recent improvements occur through scoped enumerated types.Example (non-scoped)
class Temperature { public: enum Temperature_unit {Celsius, Fahrenheit, Kelvin}; … };
Example (scoped)
enum class Medal {Gold, Silver, Bronze}; // Incorrect: 'Medal award = Gold;' Medal award = Medal::Gold; // Incorrect (matching to 'int'): 'int i = Medal::Gold;'
Example (
char
as backbone type: integral types only!)enum class Direction : char {East = 'E', South = 'S', West = 'W', North = 'N'}; assert(sizeof(Direction) == 1); // Using related 'std::underlying_type' trait: static_assert(std::is_same<std::underlying_type<Direction>::type, char>::value, "'Direction' has not 'char' as underlying type, why?");
See also
- More on C++ enumerated types in this blog
auto
(from C++ 11),
register
(until C++ 17, but already obsolete), extern
, static
,
mutable
, and thread_local
See also
Rule(s)
- Programming languages with strong (static) type checking like C++ require type declaration. Type inference is the ability to deduce the type of a variable, reducing verbose type declaration.
auto
keywordRule(s)
auto
is an old C construct. It is the “automatic” storage mechanism. Since it is the default storage mechanism,auto
was given up in C++11 to get another usage, i.e., type inference.- From C++11, variables no longer need type declaration when
auto
is used.Example
auto x = 0; // 'x' has type 'int' because '0' is by default of type 'int' auto c = 'a'; // 'char' auto d = 0.5; // 'double' auto national_debt = 14400000000000LL; // 'long long'
decltype
keywordRule(s)
decltype
is a compilation-based operator that deduces the type of an expression.Example N_INSEE.Cpp.zip
N_INSEE FB("1630125388055"); decltype(FB) JBD("1010264445244");
Example Type_inference.Cpp.zip
void f() {} … // Type of 'f': std::cout << "typeid(decltype(::f)): " << typeid(decltype(::f)).name() << std::endl; // Returned type of 'f', i.e., 'void': std::cout << "typeid(decltype(::f())): " << typeid(decltype(::f())).name() << std::endl;
Rule(s)
decltype(e)
deduces a type frome
as follows:E
whene
is a “prvalue”,E&
whene
is a “lvalue” and,E&&
whene
is a “xvalue”.Example
Person p("FranckBarbier"); // 'decltype(p.identity)' -> 'std::string' while 'decltype((p.identity))' -> 'std::string' // Compilation error (remove comments, remove error): // decltype((p.identity)) id /* = p.identity */; // std::cout << "typeid(id): " << typeid(id).name() << std::endl;
Rule(s)
std::declval
comes as an utility fordecltype
. It is essentially useful whendecltype
is concerned with generic types in templates.Example Type_inference.Cpp.zip
void g(const std::string& identity) { /* ... */ } … // Compilation error -> problem simply comes from an absence of argument: // std::cout << "typeid(decltype(::g())): " << typeid(decltype(::g())).name() << std::endl; std::cout << "typeid(decltype(::g(std::string&))): " << typeid(decltype(::g("Fictitious string!"))).name() << std::endl; // OK! std::cout << "typeid(decltype(::g(std::string&))): " << typeid(decltype(::g(std::declval<const std::string&>()))).name() << std::endl; // Even better!
Example Smart_pointers.Cpp.zip
std::unordered_map<std::string, std::unique_ptr<Mammoth>&&> zoo; // Indexed access based on names... std::cout << std::boolalpha << std::is_same_v<decltype(zoo)::value_type, std::pair<const std::string, std::unique_ptr<Mammoth>&&> > << std::endl; // 'true' decltype(zoo)::iterator i = zoo.begin(); // So, '*i' is a pair... auto&& value = (*i).second; // Type of 'value' is 'std::unique_ptr<Mammoth>&' and *NOT* 'std::unique_ptr<Mammoth>&&': static_assert(std::is_same_v<decltype(value), std::unique_ptr<Mammoth>&>, "Type checking failed, why?");
See also
decltype
andstd::declval
(in French)Structured binding
Rule(s)
- Like a reference, a structured binding is an alias to an existing object. Unlike a reference, the type of a structured binding does not have to be a reference type.
Example Lambda_expression_gcc.Cpp.zip
'.h' file: class Person { public: const std::string _given_name; const std::string _surname; std::string _nickname; inline explicit Person(const std::string& given_name, const std::string& surname, const std::string& nickname) : _given_name(given_name), _surname(surname), _nickname(nickname) { } }; '.cpp' file: char given_name[] = {'f', 'r', 'a', 'n', 'c', 'k'}; // Binding requires 6 items: auto [f, r, a, n, c, k] = given_name; // Copy 'given_name[i]' into 'f', 'r'... assert(f == 'f'); Person p("Franck", "Barbier", "?"); auto& [gn, sn, nn] = p; // References to p's (public only!) data... assert(gn.compare("Franck") == 0); assert(sn.compare("Barbier") == 0); nn = "Bab"; std::cout << p._nickname << std::endl; // 'Bab' // If argument of 'decltype' is a non-parenthesized id-expression naming a structured binding // then 'decltype' yields the referenced type: static_assert(std::is_same<decltype(nn), std::string>::value); static_assert(std::is_same<decltype((nn)), std::string&>::value);
Type casting Rule(s)
- Type casting (a.k.a. type conversion) is a key issue in daily programming. The robustness of the C++ typing system depends on casting variables that really conform to each other based on, beyond their compilation-time type, their runtime type.
- Controlled type casting relies on operators, and possibly further implementation of type-to-type interoperability.
Type interoperability
Rule(s)
explicit
keyword prevents implicit conversion and thus undesired interoperability between types.Example
'.h' file: class My_class { public: explicit My_class(int); … }; '.cpp' file: My_class::My_class(int i) { /* Some code... */ } … My_class mc0 = 0; // Compilation error comes from 'explicit'
Aware casting
Rule(s)
- C++ supports aware casting, which relies on the
static_cast
,reinterpret_cast
(no calculation compared tostatic_cast
),dynamic_cast
and,const_cast
operators. They all convert pointers or references.Example (
static_cast
)Any* my_pointer = (Any*)pointer; // Any* my_pointer = static_cast<Any*>(pointer); // Compilation error
Example (
const_cast
)// '.h' file: class Person : … { public: const std::string identity; … // '.cpp' file: Person& Person::operator=(Person const& p) { const_cast<std::string&>(this->identity) = p.identity; // Assignment overriding justifies this! …
Example (
dynamic_cast
)Animal* a = new Cat; // Cat* c = a; // Compilation error resulting from static checking... try { Cat* c = dynamic_cast<Cat*>(a); } catch (std::bad_cast& bc) { std::cerr << bc.what() << '\n'; // Try repairing here... }
Towards safer conversions
Rule(s)
- C++11 comes with the possibility of user-defined explicit conversions including template-based conversions.
Example Advanced_genericity.Cpp.zip
// '.h' file: class Male; class Female { // 'explicit' is used for Male conversion public: const std::string identity; inline Female(const std::string& identity) : identity(identity) {}; // Common conversion (transgender): explicit operator Male() const; // Refuse (implicit) 'f = m;' provided that assignment is supported as well... // Template member function (from C++11): template<typename Specie> explicit operator Specie() const { // Conversion to 'Specie', refuse "implicit" return Specie(); }; }; class Male { // 'explicit' *IS NOT* used for Female conversion public: const std::string identity; inline explicit Male(const std::string& identity) : identity(identity) {}; // Refuse (implicit) 'Male = "FranckBarbier";' // Common conversion (transgender): operator Female() const; // Accept (implicit) 'm = f;' provided that assignment is supported as well... // Template member function (from C++11): // g++ *REQUIRES* 'explicit' (compilation error) while Visual Studio doesn't: template<typename Specie> operator Specie() const { // Conversion to 'Specie', accept "implicit" return Specie(); }; }; class Minotaur { public: const std::string identity; inline Minotaur(const std::string& identity) : identity(identity) {}; }; // Explicit specialization for 'Minotaur': template<> Male::operator Minotaur() const { // Because 'Specie()' cannot work... return Minotaur(identity); } class Medusa {/* Snake woman */}; // Default no-argument constructor to match to 'Specie()' class Sphinx {/* Lion woman */}; // Explicit instantiation for 'Sphinx': template Female::operator Sphinx() const; // Useless anyway because no body... // '.cpp' file: Female::operator Male() const { return Male(identity); } Male::operator Female() const { return Female(identity); } … Male Adam("XY"); Female Eve("XX"); static_cast<Male> (Eve); // Use debugger to enter into 'explicit Female::operator Male() const' static_cast<Female> (Adam); // Use debugger to enter into 'Male::operator Female() const' static_cast<Minotaur> (Adam); // Use debugger to enter into 'template<> Male::operator Minotaur() const' static_cast<Medusa> (Eve); // Use debugger to enter into 'template<typename Specie> explicit operator Specie() const'
Introspection based on Run Time Type Information -RTTI-
Rule(s)
- C++ offers a lightweight infrastructure for introspection, which is called RunTime Type Information or RTTI (
#include <typeinfo>
).- C++ compilers like g++ generally allows RTTI by default. Nonetheless the
-fno-rtti
option of g++ disables RTTI, i.e.,typeid
anddynamic_cast
.Example RTTI.Cpp.zip
Activity_card_computation<int>* pacc; Activity_card_comp_quad<int> accq, *paccq; paccq = &accq; // Same type... pacc = &accq; // Inheritance conformance // paccq = static_cast<Activity_card_comp_quad<int>*> (pacc); // Compilation error paccq = reinterpret_cast<Activity_card_comp_quad<int>*> (pacc); // Unreliable, but this works at run-time because 'pacc' points to an instance of 'Activity_card_comp_quad<int>' const std::type_info& ti1 = typeid (paccq); // Effective type is computed based on the 'typeid' operator ('#include <typeinfo>') if (ti1 != typeid (Activity_card_comp_quad<int>*)) throw std::bad_typeid(); // Exception type through '#include <typeinfo>' std::cout << ti1.name() << std::endl; paccq = dynamic_cast<Activity_card_comp_quad<int>*> (pacc); // Reliable because conversion occurs if and only if types comply with each other const std::type_info& ti2 = typeid (paccq); if (ti2 != typeid (Activity_card_comp_quad<int>*)) throw std::bad_typeid(); std::cout << ti2.name() << std::endl;