RoadMap notes 2 Flashcards

Exception handling, language concepts, STL, templates

1
Q

Exception handling

A

a mechanism to handle errors, anomalies, or unexpected events that can occur during the runtime execution of a program. This allows the program to continue running or exit gracefully when encountering errors instead of crashing abruptly.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
2
Q

C++ provides a set of keywords and constructs for implementing exception handling:

A

try: Defines a block of code that should be monitored for exceptions.

catch: Specifies the type of exception to be caught and the block of code that shall be executed when that exception occurs.

throw: Throws an exception that will be caught and handled by the appropriate catch block.

noexcept: Specifies a function that doesn’t throw exceptions or terminates the program if an exception is thrown within its scope.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
3
Q

Exception handling example

A

int divide(int a, int b) {
if (b == 0) {
throw “Division by zero!”;
}
return a / b;
}

int main() {
int num1, num2;

std::cout << "Enter two numbers for division: ";
std::cin >> num1 >> num2;

try {
    int result = divide(num1, num2);
    std::cout << "The result is: " << result << std::endl;
} catch (const char* msg) {
    std::cerr << "Error: " << msg << std::endl;
}

define a function divide that throws an exception if b is zero. In the main function, we use a try block to call divide and output the result. If an exception is thrown, it is caught inside the catch block, which outputs an error message. This way, we can handle the error gracefully rather than letting the program crash when attempting to divide by zero.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
4
Q

Standard exceptions

A

include <stdexcept></stdexcept>

classes under the <stdexcept> library which can be used as the exception type for more specific error handling. Some of these classes include:</stdexcept>

std::exception: Base class for all standard exceptions.
std::logic_error: Represents errors which can be detected statically by the program.
std::runtime_error: Represents errors occurring during the execution of a program.

int divide(int a, int b) {
if (b == 0) {
throw std::runtime_error(“Division by zero!”);
}
return a / b;
}

int main() {
int num1, num2;

std::cout << "Enter two numbers for division: ";
std::cin >> num1 >> num2;

try {
    int result = divide(num1, num2);
    std::cout << "The result is: " << result << std::endl;
} catch (const std::exception& e) {
    std::cerr << "Error: " << e.what() << std::endl;
}

modified the divide function to throw a std::runtime_error instead of a simple string. The catch block now catches exceptions derived from std::exception and uses the member function what() to display the error message.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
5
Q

Exception handling:
try { … }

A

In the try block, you write the code that can possibly generate an exception. If an exception is encountered, the control is passed to the relevant catch block to handle the issue.

Example:

try {
// code that might throw an exception
}

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
6
Q

Exception handling:
catch( … ) { … }

A

The catch block follows the try block and is responsible for handling the exceptions thrown by the try block. There can be multiple catch blocks to handle different types of exceptions.

Example:

catch (int e) {
// handle exception of type int
}
catch (char e) {
// handle exception of type char
}
catch (…) {
// handle any other exception
}

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
7
Q

Exception handling:
throw …;

A

In case an error occurs within the try block, you can use the throw keyword to generate an exception of the specific type. This will then be caught and handled by the corresponding catch block.

Example:

try {
int num1 = 10, num2 = 0;
if (num2 == 0) {
throw “Division by zero not allowed!”;
} else {
int result = num1 / num2;
cout &laquo_space;“Result: “ &laquo_space;result &laquo_space;endl;
}
}
catch (const char* e) {
cout &laquo_space;“Error: “ &laquo_space;e &laquo_space;endl;
}

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
8
Q

Exit Codes

A

also known as “return codes” or “status codes”, are numeric values that a program returns to the calling environment (usually the operating system) when it finishes execution. These codes are used to indicate the success or failure of a program’s execution.

0 is the standard exit code for a successful execution, while non-zero exit codes typically indicate errors or other exceptional situations. The actual meanings of non-zero exit codes can vary between different applications or systems.

In C++, you can return an exit code from the main function by using the return statement, or you can use the exit() function, which is part of the C++ Standard Library.

Examples:

return 0;
std::exit(1);

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
9
Q

Access Violation

A

specific type of error that occurs when a program attempts to access an illegal memory location. In C++, access violations are most commonly caused by:

Dereferencing a null or invalid pointer.
Accessing an array out of bounds.
Reading or writing to memory freed by the user or the operating system.
It is crucial to identify access violations because they can lead to unpredictable behavior, application crashes, or corruption of data.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
10
Q

Dereferencing null or invalid pointer

A

int *p = nullptr;
int x = *p; // Access violation: trying to access null pointer’s content

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
11
Q

Accessing an array out of bounds

A

int arr[5] = {1, 2, 3, 4, 5};
int y = arr[5]; // Access violation: index out of bounds (valid indices are 0-4)

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
12
Q

Reading or writing to freed memory

A

int* p2 = new int[10];
delete[] p2;
p2[3] = 42; // Access violation: writing to memory that has been freed

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
13
Q

Debugging Access Violations

A

Tools like debuggers, static analyzers, and profilers can help identify access violations in your code. For example:

Microsoft Visual Studio: Use the built-in debugger to identify the line of code responsible for the access violation error.

Valgrind: A popular Linux tool that detects memory leaks and access violations in your C++ programs.

AddressSanitizer: A runtime memory error detector for C++ that can detect out-of-bounds accesses, memory leaks, and use-after-free errors.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
14
Q

C-style casting

A

It is the syntax inherited from C, and it is done by simply putting the target data type in parentheses before the value to cast. Example:

int a = 10;
float b = (float)a; // C-style cast from int to float

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
15
Q

static_cast

A

This is the most commonly used method for type casting in C++. It is performed at compile time, and you should use it when you have an explicit conversion between data types. Example:

int a = 10;
float b = static_cast<float>(a); // static_cast from int to float</float>

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
16
Q

dynamic_cast

A

This method is specifically used for safely converting pointers and references between base and derived classes in a class hierarchy. Example:

class Base {};
class Derived : public Base {};

Base* base_ptr = new Derived();
Derived* derived_ptr = dynamic_cast<Derived>(base_ptr); // dynamic_cast from Base to Derived*

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
17
Q

reinterpret_cast

A

This cast changes the type of a pointer, reference, or an integer value. It is also called a bitwise cast because it changes how the compiler interprets the underlying bits. Use reinterpret_cast only when you have a deep understanding of what you’re doing, as it does not guarantee that the resulting value will be meaningful. Example:

int* a = new int(42);
long b = reinterpret_cast<long>(a); // reinterpret_cast from int* to long</long>

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
18
Q

const_cast

A

This casting method is used to remove the const qualifier from a variable. It is generally not recommended, but can be useful in certain situations where you have no control over the constness of a variable. Example:

const int a = 10;
int* ptr = const_cast<int>(&a); // const_cast from const int to int*
*ptr = 20; // Not recommended, use with caution

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
19
Q

Static Cast

A

casting operators in C++ that allows you to convert between different data types, such as integer and float, or between pointer types. This type of cast performs a compile-time check and gives an error if there is no valid conversion possible between given types. static_cast is generally safer than C-style casts since it does not perform an unsafe reinterpretation of data and allows for better type checking.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
20
Q

Static cast syntax

A

static_cast<new_type>(expression)</new_type>

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
21
Q

Convert between basic data types

A

int i = 42;
float f = static_cast<float>(i); // Converts integer i to float f</float>

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
22
Q

Casting pointers of different object types in an inheritance hierarchy:

A

class Base { /* … / };
class Derived : public Base { /
… */ };

Base *bPtr = new Derived;
Derived *dPtr = static_cast<Derived *>(bPtr); // Converts Base pointer bPtr to Derived pointer dPtr

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
23
Q

Converting an integer to an enumeration

A

enum Color { RED, GREEN, BLUE };
int value = 1;
Color color = static_cast<Color>(value); // Converts integer value to corresponding Color enumeration</Color>

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
24
Q

Keep in mind that static_cast should be used with caution when casting pointers between different object types.

A

If the original type of the pointer does not match the target type, the result of the cast can be incorrect or cause unexpected behavior.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
25
Q

const_cast

A

type of casting in C++ that allows you to remove or add constness to a variable. In other words, it enables you to modify a const or volatile object, or change a pointer or reference to a const or volatile type. This is useful in certain scenarios when you need to pass a const variable as an argument or when a function parameter requires a non-const type, but you want to make sure the variable remains constant throughout the code.

Keep in mind that using const_cast to modify a truly const variable can lead to undefined behavior, so it is best to use this feature only when absolutely necessary.

void modifyVariable(int* ptr) {
*ptr = 42;
}

int main() {
const int original_value = 10;
int* non_const_value_ptr = const_cast<int*>(&original_value);
std::cout &laquo_space;“Original value: “ &laquo_space;original_value &laquo_space;std::endl;

modifyVariable(non_const_value_ptr);
std::cout << "Modified value: " << *non_const_value_ptr << std::endl;

first create a const variable, original_value. Then we use const_cast to remove the constness of the variable and assign it to a non-const pointer, non_const_value_ptr. The modifyVariable function takes an int* as an argument and modifies the value pointed to by the pointer, which would not have been possible if we passed the original const int directly. Finally, we print the original_value and the *non_const_value_ptr, which shows that the value has been modified using const_cast.

Please note that this example comes with some risks, as it touches undefined behavior. */

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
26
Q

dynamic_cast

A

type of casting operator in C++ that is used specifically for polymorphism. It safely converts pointers and references of a base class to its derived class and checks the validity of the conversion during runtime. If the conversion is not valid (i.e., the object is not of the target type), it returns a null pointer instead of producing undefined behavior. Therefore, dynamic_cast can prevent potential crashes and errors when using polymorphism.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
27
Q

dynamic_cast example

A

class BaseClass {
public:
virtual void display() {
std::cout &laquo_space;“BaseClass” &laquo_space;std::endl;
}
};

class DerivedClass : public BaseClass {
public:
void display() {
std::cout &laquo_space;“DerivedClass” &laquo_space;std::endl;
}
};

int main() {
BaseClass *basePtr = new DerivedClass(); // Upcasting
DerivedClass *derivedPtr;

derivedPtr = dynamic_cast<DerivedClass *>(basePtr);  // Downcasting
if (derivedPtr) {
    derivedPtr->display();  // Output: DerivedClass
} else {
    std::cout << "Invalid type conversion.";
}

delete basePtr;

a pointer to a DerivedClass object is assigned to a BaseClass pointer (basePtr). Then, we attempt to downcast it back to a DerivedClass pointer using dynamic_cast. If the casting is successful, we can access the DerivedClass functionality through the new pointer (derivedPtr).

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
28
Q

reinterpret_cast

A

type of casting in C++ that allows you to change the type of a pointer or an integer without altering the representation of the data. It is generally used when the conversion required is too low-level or not supported by other casting methods, such as static_cast.

Using reinterpret_cast should be handled with care, as it can lead to undefined behavior and severe problems if used incorrectly.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
29
Q

reinterpret_cast example

A

int num = 42;
int *num_ptr = &num;

// Disguise the integer pointer as a char pointer
char *char_ptr = reinterpret_cast<char *>(num_ptr);

for (size_t i = 0; i < sizeof(int); ++i) {
    // Print the individual bytes of the integer as characters
    std::cout << "Byte " << i << ": " << char_ptr[i] << std::endl;
}

using reinterpret_cast to change the type of a pointer from int * to char *, effectively treating the integer as an array of characters and printing each byte.

Remember that when using reinterpret_cast, you should be cautious about dereferencing the converted pointers. The behavior can be unpredictable, and it can lead to issues, such as accessing memory regions that are not intended to be accessed. reinterpret_cast should be used sparingly and only when a low-level conversion is necessary.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
30
Q

ADL (Argument Dependent Lookup)

A

AKA Koenig Lookup is a mechanism in C++ that allows the compiler to search for the appropriate function to call based on the types of arguments provided. It is particularly helpful when using overloaded functions or operators in a namespace.

ADL allows the compiler to find functions in the same namespace as the arguments, even if the function is not declared at the point of use or within the namespace provided. This is especially useful when working with templates or generic programming.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
31
Q

Consider the following example using a namespace and overloaded operator«():

A

namespace MyNamespace {
class MyClass {
public:
int value;
};

std::ostream& operator<<(std::ostream& os, const MyClass& obj) {
    os << "MyClass: " << obj.value;
    return os;
} }

int main() {
MyNamespace::MyClass obj;
obj.value = 42;
using std::cout;
// Required to use ‘cout’ without fully qualifying it.
cout &laquo_space;obj &laquo_space;std::endl;

when you call cout &laquo_space;obj; in main(), ADL is used to find the correct operator«() in the MyNamespace namespace because the argument obj is of type MyNamespace::MyClass.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
32
Q

Name Mangling

A

known as name decoration, is a technique used by compilers to encode additional information about the scope, type, linkage, or other identifying information of an identifier (function names, variable names, etc.) within its name. The primary purpose of name mangling is to support function overloading, which allows multiple functions with the same name but different parameter lists to coexist in a single program.

In C++, the compiler generates a mangled name for each function and variable based on their scopes and types. The mangled name is usually formed by concatenating the original name, parameter types, and other information, often using a prefix or suffix.

For example, suppose you have the following function:

int add(int a, int b)
{
return a + b;
}
The compiler might generate a mangled name such as _Z3addii, which encodes the function name add and its two int parameters.

The exact rules for name mangling are implementation and platform dependent. Different compilers may mangle names differently, which can lead to incompatibilities when attempting to link together object files or libraries compiled with different compilers.

Some tools, such as c++filt (included in GCC and Clang), can be used to demangle a mangled name back to the original identifier, which can be useful when debugging or working with symbol tables.

$ echo “_Z3addii” | c++filt
add(int, int)

33
Q

Macros

A

preprocessing directives in C++ used by the preprocessor to perform text substitution. They are defined using the #define directive, followed by the macro name and the value to be substituted.

Macros can be used to define constants, create function-like macros, or perform conditional compilation.

34
Q

Constant Macros

A

define PI 3.14159

used to define symbolic constants for use in code. They do not use any memory and are replaced by the preprocessor before the compilation process.

Example:

This macro defines a symbolic constant PI. You can use it in your code as if it were a regular variable.

double circumference = 2 * PI * radius;

35
Q

Function-like Macros

A

define SQUARE(x) ((x) * (x))

similar to regular functions. They take a list of arguments and perform text substitution.

Example:

This macro defines a function-like macro SQUARE that calculates the square of a number.

int square_of_five = SQUARE(5); // expands to ((5) * (5))

36
Q

Conditional Compilation

A

define DEBUG_MODE

Macros can be used for conditional compilation using the #ifdef, #ifndef, #if, #else, #elif, and #endif directives.

Example:

// Code to be compiled only in debug mode
#else
// Code to be compiled only if DEBUG_MODE is not defined
#endif
This example demonstrates how you can use macros to control the parts of code that are being compiled, depending on the presence or absence of a macro definition.

37
Q

iterators

A

objects in the C++ Standard Library (STL) that help us traverse containers like arrays, lists, and vectors. Essentially, they act as a bridge between container classes and algorithms. Iterators behave similar to pointers but provide a more generalized and abstract way of accessing elements in a container.

38
Q

input iterator

A

Used to read elements in a container only once, in a forward direction. They cannot modify elements.
Example:

std::vector<int> nums = {1, 2, 3, 4};
std::istream_iterator<int> input(std::cin);
std::copy(input, std::istream_iterator<int>(), std::back_inserter(nums));</int></int></int>

39
Q

output iterator

A

Used to write elements in a container only once, in a forward direction. They cannot re-write elements.
Example:

std::vector<int> nums = {1, 2, 3, 4};
std::ostream_iterator<int> output(std::cout, ", ");
std::copy(nums.begin(), nums.end(), output);</int></int>

40
Q

Forward iterator

A

Similar to input iterators but can be used for multiple passes over the elements in a container. They cannot move backward.
Example:

std::forward_list<int> nums = {1, 2, 3, 4};
std::forward_list<int>::iterator itr = nums.begin();
while (itr != nums.end()) {
std::cout << *itr << " ";
\++itr;
}</int></int>

41
Q

Bidirectional Iterator

A

These iterators offer the ability to move both forward and backward in a container. List and set containers have bi-directional iterators.
Example:

std::list<int> nums = {1, 2, 3, 4};
std::list<int>::iterator itr;
for (itr = nums.begin(); itr != nums.end(); ++itr) {
std::cout << *itr << " ";
}
for (--itr; itr != nums.begin(); --itr) {
std::cout << *itr << " ";
}</int></int>

42
Q

Random Access Iterator

A

These iterators provide the most flexible ways to access elements in a container. They can move forwards, backwards, jump directly to other elements, and access elements at a given index.
Example:

std::vector<int> nums = {1, 2, 3, 4};
std::vector<int>::iterator itr;
for (itr = nums.begin(); itr != nums.end(); ++itr) {
std::cout << *itr << " ";
}
for (itr -= 1; itr != nums.begin() - 1; --itr) {
std::cout << *itr << " ";
}</int></int>

43
Q

For most cases, you would want to start with the auto keyword and the appropriate container methods (like begin() and end()) to work with iterators.

A

std::vector<int> nums = {1, 2, 3, 4};
for (auto itr = nums.begin(); itr != nums.end(); ++itr) {
std::cout << *itr << " ";
}</int>

44
Q

When working with algorithms, remember that the C++ Standard Library provides

A

various algorithms that already utilize iterators for tasks like searching, sorting, and manipulating elements.

45
Q

std::sort

A

include <algorithm></algorithm>

Sorting refers to arranging a sequence of elements in a specific order. The STL provides several sorting algorithms, such as std::sort, std::stable_sort, and std::partial_sort.

used to sort a range of elements [first, last) in non-descending order (by default). You can also use custom comparison functions or lambda expressions to change the sorting order.

Example:

std::vector<int> nums = {10, 9, 8, 7, 6, 5};
std::sort(nums.begin(), nums.end());
46
Q

Searching STL algorithm

A

Searching refers to finding if a particular element is present within a given range of elements. STL provides various searching algorithms, such as std::find, std::binary_search, and std::find_if.

47
Q

std::find

A

used to find the iterator of the first occurrence of a given value within the range [first, last).

std::vector<int> nums = {5, 6, 7, 8, 9, 10};
auto it = std::find(nums.begin(), nums.end(), 9);</int>

48
Q

Modifying sequences STL algorithm

A

STL also provides algorithms for modifying sequences, such as std::remove, std::replace, and std::unique.

49
Q

std::remove

A

std::vector<int> nums = {5, 6, 7, 6, 8, 6, 9, 6, 10};
nums.erase(std::remove(nums.begin(), nums.end(), 6), nums.end());</int>

50
Q

Duration

A

represents a span of time, which can be expressed in various units such as seconds, minutes, hours, etc. To create a duration, use the std::chrono::duration template class. Common predefined duration types are:

std::chrono::seconds
std::chrono::minutes
std::chrono::hours

std::chrono::seconds sec(5);
std::chrono::minutes min(2);
std(chrono)::hours hr(1);

51
Q

time_point

A

include<chrono></chrono>

represents a specific point in time. It is usually created using a combination of duration and a clock. In C++, there are three clock types provided by the chrono library:

std::chrono::system_clock: Represents the system-wide real time wall clock.
std::chrono::steady_clock: Represents a monotonic clock that is guaranteed to never be adjusted.
std::chrono::high_resolution_clock: Represents the clock with the shortest tick period.

std::chrono::system_clock::time_point tp = std::chrono::system_clock::now();

52
Q

Clock

A

provides access to the current time. It consists of the following elements:

time_point: A specific point in time.
duration: The time duration between two time points.
now(): A static function that returns the current time point

// Get the current time_point using system_clock
std::chrono::system_clock::time_point now = std::chrono::system_clock::now();

// Get the time_point 1 hour from now
std::chrono::system_clock::time_point one_hour_from_now = now + std::chrono::hours(1);
53
Q

convert a time point to calendar representation, you can use the std::chrono::system_clock::to_time_t function.

A

std::chrono::system_clock::time_point now = std::chrono::system_clock::now();
std::time_t now_c = std::chrono::system_clock::to_time_t(now);
std::cout &laquo_space;“Current time: “ &laquo_space;std::ctime(&now_c) &laquo_space;std::endl;

54
Q

Multithreading

A

concurrent execution of multiple threads within a single process or program. It improves the performance and efficiency of an application by allowing multiple tasks to be executed in parallel.

In C++, multithreading support is available through the thread library introduced in the C++11 standard.

55
Q

To create a new thread, include the <thread> header file and create an instance of std::thread that takes a function as an argument. The function will be executed in a new thread.</thread>

A

void my_function() {
std::cout &laquo_space;“This function is executing in a separate thread” &laquo_space;std::endl;
}

int main() {
std::thread t(my_function);
t.join(); // waits for the thread to complete

56
Q

You can pass arguments to the thread function by providing them as additional arguments to the std::thread constructor.

A

include <thread></thread>

void print_sum(int a, int b) {
std::cout &laquo_space;“The sum is: “ &laquo_space;a + b &laquo_space;std::endl;
}

int main() {
std::thread t(print_sum, 3, 5);
t.join();

57
Q

When multiple threads access shared resources, there is a possibility of a data race. To avoid this, use mutex and locks to synchronize shared resource access.

A

include <mutex></mutex>

#include <thread></thread>

std::mutex mtx;

void print_block(int n, char c) {
{
std::unique_lock<std::mutex> locker(mtx);
for (int i = 0; i < n; ++i) {
std::cout &laquo_space;c;
}
std::cout &laquo_space;std::endl;
}
}

int main() {
std::thread t1(print_block, 50, ‘*’);
std::thread t2(print_block, 50, ‘$’);

t1.join();
t2.join();
58
Q

Vector

A

include<vector></vector>

dynamic arrays that can resize themselves as needed. They store elements in a contiguous memory location, allowing fast random access using indices.

std::vector<int> vec = {1, 2, 3, 4, 5};</int>

vec.push_back(6);
59
Q

List

A

include<list></list>

doubly-linked list that allows elements to be inserted or removed from any position in constant time. It does not support random access. Lists are better than vectors for scenarios where you need to insert or remove elements in the middle frequently.

std::list<int> lst = {1, 2, 3, 4, 5};</int>

lst.push_back(6);
60
Q

Map

A

include<map>

an associative container that stores key-value pairs. It supports the retrieval of values based on their keys. The keys are sorted in ascending order by default.

std::map<std::string, int> m;

m["one"] = 1;
m["two"] = 2;

for (const auto &pair : m) {
std::cout &laquo_space;pair.first &laquo_space;”: “ &laquo_space;pair.second &laquo_space;std::endl;
}

61
Q

unordered_map

A

include<unordered_map></unordered_map>

Similar to a map, an unordered map stores key-value pairs, but it is implemented using a hash table. This means unordered_map has faster average-case performance compared to map, since it does not maintain sorted order. However, worst-case performance can be worse than map.

std::unordered_map<std::string, int> um;

um["one"] = 1;
um["two"] = 2;

for (const auto &pair : um) {
std::cout &laquo_space;pair.first &laquo_space;”: “ &laquo_space;pair.second &laquo_space;std::endl;
}

62
Q

Template functions

A

template <typename>
T max(T a, T b) {
return (a > b) ? a : b;
}
To use this function, you can either explicitly specify the type parameter:</typename>

int result = max<int>(10, 20);
Or, you can let the compiler deduce the type for you:</int>

int result = max(10, 20);

63
Q

Template classes

A

template <typename T1, typename T2>
class Pair {
public:
T1 first;
T2 second;

Pair(T1 first, T2 second) : first(first), second(second) {} }; To use this class, you need to specify the type parameters when creating an object:

Pair<int, std::string> pair(1, “Hello”);

64
Q

Template specialization

A

Sometimes, you may need special behavior for a specific data type. In this case, you can use template specialization. For example, you can specialize the Pair class for a specific type, like char:

template <>
class Pair<char, char> {
public:
char first;
char second;

Pair(char first, char second) : first(first), second(second) {
    // Special behavior for characters (e.g., convert to uppercase)
    this->first = std::toupper(this->first);
    this->second = std::toupper(this->second);
} };

Now, when you create a Pair object with char template arguments, the specialized behavior will be used:

Pair<char, char> charPair(‘a’, ‘b’);

65
Q

Variadic templates

A

a feature in C++11 that allows you to define a template with a variable number of arguments. This is especially useful when you need to write a function or class that can accept different numbers and types of arguments.

66
Q

Variadic template syntax

A

The syntax for variadic templates is very simple. To define a variadic template, use the … (ellipsis) notation:

template <typename...>
This notation represents a parameter pack, which can contain zero or more arguments. You can use this parameter pack as a variable list of template parameters in your template definition.</typename...>

67
Q

Summing Multiple arguments using variadic templates

A

// Base case for recursion
template <typename>
T sum(T t) {
return t;
}</typename>

// Variadic template
template <typename T, typename… Args>
T sum(T t, Args… args) {
return t + sum(args…);
}

68
Q

Tuple class using variadic templates

A

template <typename...>
class Tuple;</typename...>

// Base case: empty tuple
template <>
class Tuple<> {};

// Recursive case: Tuple with one or more elements
template <typename Head, typename… Tail>
class Tuple<Head, Tail…> : public Tuple<Tail...> {
public:
Tuple(Head head, Tail... tail) : Tuple<Tail...>(tail...), head_(head) {}</Tail...></Tail...>

Head head() const { return head_; }

private:
Head head_;
};

int main() {
Tuple<int, float, double> tuple(1, 2.0f, 3.0);

69
Q

Template specialization

A

a way to customize or modify the behavior of a template for a specific type or a set of types. This can be useful when you want to optimize the behavior or provide specific implementation for a certain type, without affecting the overall behavior of the template for other types.

70
Q

Full specialization

A

This occurs when you provide a specific implementation for a specific type or set of types.

71
Q

Partial specialization

A

This occurs when you provide a more general implementation for a subset of types that match a certain pattern or condition.

72
Q

Full template specialization

A

used when you want to create a separate implementation of a template for a specific type. To do this, you need to use keyword template<> followed by the function template with the desired specialized type.

template <typename>
void printData(const T& data) {
std::cout << "General template: " << data << std::endl;
}</typename>

template <>
void printData(const char& data) {
std::cout &laquo_space;“Specialized template for const char
: “ &laquo_space;data &laquo_space;std::endl;
}

73
Q

Partial template specialization

A

used when you want to create a separate implementation of a template for a subset of types that match a certain pattern or condition.

template <typename K, typename V>
class MyPair {
public:
MyPair(K k, V v) : key(k), value(v) {}

void print() const {
    std::cout << "General template: key = " << key << ", value = " << value << std::endl;
}

private:
K key;
V value;
};

template <typename>
class MyPair<T, int> {
public:
MyPair(T k, int v) : key(k), value(v) {}</typename>

void print() const {
    std::cout << "Partial specialization for int values: key = " << key
              << ", value = " << value << std::endl;
}

private:
T key;
int value;
};

int main() {
MyPair<double, std::string> p1(3.2, “example”);
MyPair<char, int> p2(‘A’, 65);
p1.print(); // General template: key = 3.2, value = example
p2.print();

74
Q

Type traits

A

set of template classes in C++ that help in getting the information about the type’s properties, behavior, or characteristics. They can be found in the <type_traits> header file. By using Type Traits, you can adapt your code depending on the properties of a given type, or even enforce specific properties for your type parameters in template code.</type_traits>

75
Q

Common type traits

A

std::is_pointer: Checks if a given type is a pointer type.

std::is_arithmetic: Checks if the given type is an arithmetic type.

std::is_function: Checks if the given type is a function type.

std::decay: Applies decltype rules to the input type ( strips references, cv-qualifiers, etc. ).

76
Q

type_traits example

A

include <type_traits></type_traits>

int main() {
int a;
int* a_ptr = &a;

std::cout << "Is 'a' a pointer? " << std::boolalpha << std::is_pointer<decltype(a)>::value << std::endl;
std::cout << "Is 'a_ptr' a pointer? " << std::boolalpha << std::is_pointer<decltype(a_ptr)>::value << std::endl;
77
Q

Composing type traits

A

Some type traits help you compose other traits or modify them, such as:

std::conditional: If a given boolean value is true, use type A; otherwise, use type B.
std::enable_if: If a given boolean value is true, use type A; otherwise, there is no nested type.
#include <iostream>
#include <type_traits></type_traits></iostream>

template <typename>
typename std::enable_if<std::is_arithmetic<T>::value, T>::type find_max(T a, T b) {
return a > b ? a : b;
}</T></typename>

int main() {
int max = find_max(10, 20);

the find_max template function is only defined when T is an arithmetic type (e.g., int, float, double). This prevents unintended usage of the find_max function with non-arithmetic types.

Overall, type traits are a powerful tool to create more generic, extensible, and efficient C++ code, providing a way to query and adapt your code based on type characteristics.

78
Q

SFINAE (Substitution Failure Is Not An Error)

A

a principle in C++ template metaprogramming that allows the compiler to select the appropriate function or class when a specific template specialization fails during substitution. The term “substitution failure” refers to the process where the compiler tries to substitute template arguments into a function template or class template. If the substitution causes an error, the compiler won’t consider that specific specialization as a candidate and will continue searching for a valid one.

The key idea behind SFINAE is that if a substitution error occurs, it is silently ignored, and the compiler continues to explore other template specializations or overloads. This allows you to write more flexible and generic code, as it enables you to have multiple specializations for different scenarios.

79
Q

SFINAE example

A

template <typename T, typename = std::enable_if_t<std::is_arithmetic<T>::value>>
void foo(T t) {
std::cout << "Called when T is arithmetic" << std::endl;
}</T>

template <typename T, typename = std::enable_if_t<!std::is_arithmetic<T>::value>>
void foo(T t) {
std::cout << "Called when T is not arithmetic" << std::endl;
}</T>

int main() {
int a = 5;
foo(a); // output: Called when T is arithmetic

std::string s = "example";
foo(s); // output: Called when T is not arithmetic }

define two foo function template specializations. The first one is enabled when T is an arithmetic type, while the second one is enabled when T is not an arithmetic type. The std::enable_if_t inside the template parameter list allows us to control which specialization is valid for a given type of T.

When calling foo(a) with an integer, the first specialization is selected, and when calling foo(s) with a string, the second specialization is selected. If there is no valid specialization, the code would fail to compile.