C++ data types


Creative Commons License
This -C++ data types- tutorial is licensed under a Creative Commons Attribution-NonCommercial 4.0 International License
Preamble

C++ primitive types like char, short, or int are the heritage of primitive types in the C programming language. C++ adds a short list of its own primitive types like bool plus a long list of non-primitive types like std::string; it is the possibility of introducing new types based on, mostly, the class construct/keyword.

Headlines
Primitive types

Rule(s)

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)

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)

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)

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'
Non-primitive types

Rule(s)

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)

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)

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

Data structures

Rule(s)

Example (.h header file) Numbers.Cpp.zip 

struct 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.zip 

My_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

Arrays

Rule(s)

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)

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'
Enumerated types

Rule(s)

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

Storage-based modifiers: auto (from C++ 11), register (until C++ 17, but already obsolete), extern, static, mutable, and thread_local

See also

Type inference

Rule(s)

auto keyword

Rule(s)

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 keyword

Rule(s)

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)

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)

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

Structured binding

Rule(s)

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 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 to static_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 and dynamic_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;