Loading...

Go Back

Next page
Go Back Course Outline

C++ Full Course


Morden C++

Modern C++ Features

Modern C++ standards (C++11, C++14, C++17, and C++20) have introduced numerous features that make the language more expressive, safer, and more efficient. Let's explore some of these key additions.

Auto & Decltype (C++11)

auto and decltype provide powerful mechanisms for type deduction, reducing verbosity and improving code flexibility.

auto

auto allows the compiler to deduce the type of a variable from its initializer. This is particularly useful for complex types or when the type is obvious from the initialization.

#include <iostream>
#include <vector>
#include <map>

int main() {
    auto x = 10; // x is deduced as int
    auto y = 3.14; // y is deduced as double
    auto z = "hello"; // z is deduced as const char*

    std::vector<int> numbers = {1, 2, 3, 4, 5};
    for (auto it = numbers.begin(); it != numbers.end(); ++it) {
        std::cout << *it << " ";
    }
    std::cout << std::endl;

    std::map<std::string, int> ages = {{"Alice", 30}, {"Bob", 25}};
    for (auto const& [name, age] : ages) { // Using structured binding (C++17) with auto
        std::cout << name << ": " << age << std::endl;
    }

    return 0;
}
1 2 3 4 5
Alice: 30
Bob: 25

decltype (C++11)

decltype inspects the declared type of an entity or the type of an expression without actually evaluating the expression.

#include <iostream>
#include <typeinfo>

int main() {
    int x = 10;
    decltype(x) y = 20; // y has the same type as x (int)
    std::cout << "Type of y: " << typeid(y).name() << ", Value of y: " << y << std::endl;

    auto add = [](int a, int b) -> int { return a + b; };
    decltype(add) anotherAdd = add; // anotherAdd has the same type as the lambda 'add'
    std::cout << "Result of anotherAdd(5, 3): " << anotherAdd(5, 3) << std::endl;

    return 0;
}
Type of y: i, Value of y: 20
Result of anotherAdd(5, 3): 8

Range-Based Loops (C++11)

Range-based for loops provide a more concise and readable way to iterate over elements in containers and other ranges.

#include <iostream>
#include <vector>
#include <string>

int main() {
    std::vector<int> numbers = {10, 20, 30, 40, 50};
    std::cout << "Numbers: ";
    for (auto& num : numbers) { // Iterate by reference to allow modification
        num *= 2;
        std::cout << num << " ";
    }
    std::cout << std::endl;

    std::string message = "Hello";
    std::cout << "Characters in message: ";
    for (char c : message) { // Iterate by value (copy)
        std::cout << c << " ";
    }
    std::cout << std::endl;

    return 0;
}
Numbers: 20 40 60 80 100
Characters in message: H e l l o


Uniform Initialization (C++11)

Uniform initialization allows you to use brace initialization {} for all types of objects, providing a more consistent and less error-prone way to initialize variables.

#include <iostream>
#include <vector>
#include <complex>

struct MyStruct {
    int x;
    double y;
};

int main() {
    int a{10};
    double b{3.14};
    std::vector<int> v{1, 2, 3};
    std::complex<double> c{1.0, 2.0};
    MyStruct s{5, 2.7};
    int arr[]{100, 200, 300};

    std::cout << "a: " << a << std::endl;
    std::cout << "b: " << b << std::endl;
    std::cout << "v: "; for (int val : v) std::cout << val << " "; std::cout << std::endl;
    std::cout << "c: " << c << std::endl;
    std::cout << "s: {" << s.x << ", " << s.y << "}" << std::endl;
    std::cout << "arr: "; for (int val : arr) std::cout << val << " "; std::cout << std::endl;

    return 0;
}
a: 10
b: 3.14
v: 1 2 3
c: (1,2)
s: {5, 2.7}
arr: 100 200 300

Variadic Templates (C++11)

Variadic templates allow you to define templates that can take a variable number of arguments (both type and non-type). This is often used for features like std::printf or for implementing tuple-like structures.

Parameter Packs

A parameter pack is a template parameter that accepts zero or more template arguments. Within the template definition, you can use an ellipsis (...) to indicate a parameter pack.

Fold Expressions (C++17)

Fold expressions provide a concise way to perform operations over a parameter pack.

#include <iostream>
#include <string>

// Base case for recursive print (when no arguments are left)
void print() {
    std::cout << std::endl;
}

// Recursive template function to print arguments
template <typename T, typename... Args>
void print(T first, Args... rest) {
    std::cout << first << " ";
    print(rest...); // Recursive call with the rest of the arguments
}

// Fold expression (C++17) to sum all arguments
template <typename... Args>
auto sum(Args... args) {
    return (args + ...); // Binary left fold
}

// Fold expression (C++17) to concatenate all strings
template <typename... Args>
auto concatenate(Args... args) {
    return (std::string("") + ... + args); // Binary left fold with initial value
}

int main() {
    print(1, 2.5, "hello", 'c');
    std::cout << "Sum: " << sum(1, 2, 3, 4, 5) << std::endl;
    std::cout << "Concatenation: " << concatenate("This ", "is ", "a ", "string.") << std::endl;
    return 0;
}
1 2.5 hello c
Sum: 15
Concatenation: This is a string.


Concepts & Constraints (C++20)

Concepts provide a way to specify requirements on template arguments, making template usage clearer and improving compiler error messages. Constraints are used to enforce these requirements.

#include <iostream>
#include <concepts>

// Define a concept for types that support the less-than operator
template <typename T>
concept LessThanComparable = requires(T a, T b) {
    { a < b } -> std::convertible_to<bool>;
};

// Template function that requires LessThanComparable
template <LessThanComparable T>
bool isSmaller(const T& a, const T& b) {
    return a < b;
}

// Another way to use concepts in function templates
template <typename T>
requires LessThanComparable<T>
bool isSmallerAlternative(const T& a, const T& b) {
    return a < b;
}

struct NotComparable {};

int main() {
    std::cout << "Is 5 < 10? " << std::boolalpha << isSmaller(5, 10) << std::endl;
    std::cout << "Is 3.14 < 2.71? " << std::boolalpha << isSmaller(3.14, 2.71) << std::endl;

    std::cout << "Is 'a' < 'z'? " << std::boolalpha << isSmallerAlternative('a', 'z') << std::endl;

    // The following line would result in a compile error because NotComparable
    // does not satisfy the LessThanComparable concept.
    // isSmaller(NotComparable{}, NotComparable{});

    return 0;
}
Is 5 < 10? true
Is 3.14 < 2.71? false
Is 'a' < 'z'? true

Modules (C++20)

Modules are a significant improvement over traditional header files. They aim to provide better build times, prevent macro pollution, and improve encapsulation.

Due to the complexity of setting up and compiling modules, a simple runnable code example within this HTML context is not feasible. However, the basic idea is to replace #include directives with import statements for pre-compiled module interfaces.


        // my_module.ixx (module interface unit)
        export module my_module;
        export int add(int a, int b);

        // my_module.cpp (module implementation unit)
        module my_module;
        int add(int a, int b) {
            return a + b;
        }

        // main.cpp
        import my_module;
        #include <iostream>

        int main() {
            std::cout << "Sum: " << add(5, 3) << std::endl;
            return 0;
        }
    

Key benefits of modules include faster compilation (as interfaces are compiled once), no macro pollution (macros are not exported or imported), and stronger encapsulation (only exported names are visible).

These modern C++ features collectively contribute to writing more robust, efficient, and maintainable code.

Go Back

Next page