Modern C++ has seen a series of significant updates starting from C++11, each bringing new features and enhancements that aim to make the language more efficient, readable, and maintainable. Here’s a brief overview of the major features introduced in each version since C++11, along with a comment on their usage:
C++11
C++11 marked a significant evolution in the C++ language, introducing several powerful features that modernized and simplified C++ programming. Here are some of the most impactful features, along with examples to illustrate their usage:
1. Auto type Deduction
The auto
keyword allows the compiler to automatically deduce the type of a variable from its initializer.
auto x = 42; // int auto y = 3.14; // double auto s = "hello"; // const char*
2. Lambda Expressions
Lambdas provide a concise way to define anonymous functions directly in your code.
auto add = [](int a, int b) { return a + b; }; int result = add(3, 4); // result is 7
3. Range-based for Loops
Simplifies iteration over collections like arrays, vectors, and other containers.
std::vector<int> numbers = {1, 2, 3, 4, 5}; for (int n : numbers) { std::cout << n << " "; }
4. Smart Pointers
Introduces std::unique_ptr
and std::shared_ptr
to manage dynamic memory automatically, preventing memory leaks.
std::unique_ptr<int> ptr(new int(10)); // No need to delete ptr manually; it will be deleted when it goes out of scope
5. Move Semantics
Move semantics optimize performance by allowing resources to be transferred rather than copied.
std::vector<int> makeVector() { std::vector<int> v = {1, 2, 3}; return v; // Moves v rather than copying } std::vector<int> v = makeVector(); // Efficient move
6. nullptr
Replaces NULL
with nullptr
to represent null pointers more safely.
int* p = nullptr; // p is a null pointer
7. constexpr
Allows for compile-time constant expressions, improving performance by performing computations at compile time.
constexpr int square(int x) { return x * x; } int arr[square(5)]; // Creates an array of size 25
8. std::thread
Provides a standard way to create and manage threads, enabling concurrent programming.
#include <thread> #include <iostream> void hello() { std::cout << "Hello from thread!" << std::endl; } int main() { std::thread t(hello); t.join(); // Wait for thread to finish return 0; }
9. Variadic Templates
Supports templates with a variable number of arguments, making template programming more flexible.
template<typename... Args> void print(Args... args) { (std::cout << ... << args) << std::endl; // Fold expression (C++17) } print(1, 2, "three", 4.0);
10. Uniform Initialization
Enables a consistent syntax for initializing variables and containers.
int arr[] = {1, 2, 3}; std::vector<int> v = {1, 2, 3, 4, 5}; struct Point { int x, y; }; Point p = {1, 2};
C++14
C++14 built upon the foundation of C++11, introducing several important enhancements to improve code readability, flexibility, and performance. Here are the major features with examples:
1. Generic Lambdas
C++14 allows the use of auto
in lambda parameter lists, making lambdas more flexible and easier to use with generic code.
Example:
auto add = [](auto a, auto b) { return a + b; }; std::cout << add(1, 2) << std::endl; // Output: 3 std::cout << add(1.5, 2.3) << std::endl; // Output: 3.8
2. Variable Templates
Variable templates enable the definition of templates for variables, not just functions or classes.
Example:
template<typename T> constexpr T pi = T(3.1415926535897932385L); std::cout << pi<double> << std::endl; // Output: 3.14159 std::cout << pi<float> << std::endl; // Output: 3.14159f
3. Return Type Deduction
C++14 allows functions to deduce their return type automatically using auto
.
Example:
auto add(int a, int b) { return a + b; } std::cout << add(1, 2) << std::endl; // Output: 3
4. std::make_unique
This utility function simplifies the creation of std::unique_ptr
instances, making the code more readable and less error-prone.
Example:
#include <memory> auto ptr = std::make_unique<int>(42); std::cout << *ptr << std::endl; // Output: 42
5. Deprecated Attribute
The [[deprecated]]
attribute can be used to mark functions and variables as deprecated, generating compile-time warnings when they are used.
Example:
[[deprecated("Use new_function() instead")]] void old_function() {} void new_function() {} int main() { old_function(); // Warning: 'old_function' is deprecated: Use new_function() instead new_function(); }
6. Binary Literals and Digit Separators
C++14 introduced binary literals prefixed with 0b
or 0B
and the single quotation mark as a digit separator to improve readability of large numbers.
Example:
int binary = 0b1010; // Binary literal int largeNumber = 1'000'000; // Digit separator std::cout << binary << std::endl; // Output: 10 std::cout << largeNumber << std::endl; // Output: 1000000
C++17
C++17 brought a plethora of new features and enhancements, making the language more expressive and user-friendly. Here are some of the major additions with detailed explanations and examples:
1.std::optional
std::optional
is a utility that represents a value that may or may not be present. It’s useful for functions that might not return a value.
Example:
#include <optional> #include <iostream> std::optional<int> find_even_number(int num) { if (num % 2 == 0) return num; return std::nullopt; } int main() { auto result = find_even_number(4); if (result) { std::cout << "Even number: " << *result << "\n"; } else { std::cout << "Not an even number\n"; } }
2.std::variant
std::variant
is a type-safe union, allowing a variable to hold one of several specified types.
Example:
#include <variant> #include <iostream> int main() { std::variant<int, float, std::string> my_variant; my_variant = 10; std::cout << std::get<int>(my_variant) << "\n"; my_variant = 3.14f; std::cout << std::get<float>(my_variant) << "\n"; my_variant = "Hello"; std::cout << std::get<std::string>(my_variant) << "\n"; }
3.std::any
std::any
is a type-safe container for single values of any type. It’s useful when the type of the value is not known at compile time.
Example:
#include <any> #include <iostream> int main() { std::any my_any; my_any = 42; std::cout << std::any_cast<int>(my_any) << "\n"; my_any = std::string("Hello"); std::cout << std::any_cast<std::string>(my_any) << "\n"; }
4. Structured Bindings
Structured bindings allow for unpacking tuple-like objects directly into individual variables.
Example:
#include <tuple> #include <iostream> std::tuple<int, float, std::string> get_data() { return {1, 2.3f, "test"}; } int main() { auto [i, f, s] = get_data(); std::cout << "i: " << i << ", f: " << f << ", s: " << s << "\n"; }
5.if constexpr
if constexpr
enables compile-time conditional compilation.
Example:
#include <iostream> template <typename T> void print_type_info(T value) { if constexpr (std::is_integral_v<T>) { std::cout << "Integral type\n"; } else { std::cout << "Non-integral type\n"; } } int main() { print_type_info(42); print_type_info(3.14); }
6. Fold Expressions
Fold expressions simplify the use of variadic templates by providing a concise syntax for operations on parameter packs.
Example:
#include <iostream> template<typename... Args> auto sum(Args... args) { return (... + args); // fold expression } int main() { std::cout << "Sum: " << sum(1, 2, 3, 4, 5) << "\n"; }
7. Filesystem Library
The <filesystem>
library provides facilities for performing operations on file systems and their components.
Example:
#include <filesystem> #include <iostream> int main() { std::filesystem::path p{"example.txt"}; if (std::filesystem::exists(p)) { std::cout << p << " exists\n"; } else { std::cout << p << " does not exist\n"; } }
These features collectively enhance the power and flexibility of C++, making it easier to write robust, modern, and efficient code.
C++20
C++20 is a milestone in the evolution of C++, bringing a host of powerful features that enhance the language’s expressiveness, safety, and performance. Here’s a detailed look at the major additions with examples:
1. Concepts
Concepts provide a way to specify constraints on template parameters, making templates more readable and error messages more understandable.
#include <concepts> #include <iostream> template <typename T> concept Integral = std::is_integral_v<T>; template <Integral T> T add(T a, T b) { return a + b; } int main() { std::cout << add(3, 4) << '\n'; // Works // std::cout << add(3.0, 4.0) << '\n'; // Error: double doesn't satisfy Integral }
2. Ranges
The Ranges library introduces a new way to work with sequences of data, making code more readable and expressive.
#include <ranges> #include <vector> #include <iostream> int main() { std::vector<int> v = {1, 2, 3, 4, 5}; auto result = v | std::ranges::views::filter([](int n) { return n % 2 == 0; }); for (int n : result) { std::cout << n << ' '; // Output: 2 4 } }
3. Coroutines
Coroutines allow for writing asynchronous code in a sequential style, simplifying the development of concurrent applications.
#include <coroutine> #include <iostream> struct ReturnObject { struct promise_type { ReturnObject get_return_object() { return {}; } std::suspend_never initial_suspend() { return {}; } std::suspend_never final_suspend() noexcept { return {}; } void unhandled_exception() {} void return_void() {} }; }; ReturnObject foo() { std::cout << "Hello "; co_await std::suspend_always{}; std::cout << "World\n"; } int main() { auto handle = foo(); handle.resume(); handle.resume(); }
4. Modules
Modules provide a new way to organize and import code, improving compile times and code encapsulation.
// my_module.ixx export module my_module; export int add(int a, int b) { return a + b; } // main.cpp import my_module; #include <iostream> int main() { std::cout << add(3, 4) << '\n'; // Output: 7 }
5. Three-way Comparison (Spaceship Operator)
The three-way comparison operator (<=>
) simplifies the implementation of comparisons and provides a consistent way to handle them.
#include <compare> #include <iostream> struct Point { int x, y; auto operator<=>(const Point&) const = default; }; int main() { Point p1{1, 2}, p2{2, 3}; if (p1 < p2) { std::cout << "p1 is less than p2\n"; // Output } }
6. Calendar and Time Zone Library
This library provides comprehensive support for handling dates, times, and time zones.
#include <chrono> #include <iostream> int main() { using namespace std::chrono; auto now = system_clock::now(); auto today = floor<days>(now); std::cout << "Today is: " << today.time_since_epoch().count() << " days since epoch\n"; }
C++20 significantly enriches the language, making it more modern and powerful for a wide range of programming tasks. These features together improve the robustness, readability, and maintainability of C++ code.
C++23
C++23 introduces several powerful features that enhance the language’s capabilities, safety, and ease of use. Here’s a detailed look at some of the most significant additions:
1. std::expected
std::expected
is a new utility for error handling, similar to std::optional
but with built-in error state management.
Example:
#include <iostream> #include <expected> std::expected<int, std::string> divide(int a, int b) { if (b == 0) { return std::unexpected("Division by zero!"); } return a / b; } int main() { auto result = divide(10, 0); if (!result) { std::cout << "Error: " << result.error() << '\n'; } else { std::cout << "Result: " << *result << '\n'; } }
2.std::mdspan
std::mdspan
is a multi-dimensional array view, providing a way to describe the shape and layout of data in memory.
Example:
#include <iostream> #include <mdspan> int main() { int data[6] = {1, 2, 3, 4, 5, 6}; std::mdspan<int, std::extents<2, 3>> mdspan(data); for (int i = 0; i < 2; ++i) { for (int j = 0; j < 3; ++j) { std::cout << mdspan(i, j) << ' '; } std::cout << '\n'; } }
3. Reflection (Experimental)
C++23 introduces experimental reflection capabilities, allowing compile-time introspection of types and their properties.
Example:
#include <iostream> #include <experimental/reflect> struct MyStruct { int a; double b; void foo() {} }; int main() { auto type = reflexpr(MyStruct); for (auto member : type.get_data_members()) { std::cout << "Member name: " << member.get_name() << '\n'; } }
4.if consteval
The if consteval
statement allows code to check if it is being evaluated at compile-time.
Example:
#include <iostream> constexpr int factorial(int n) { if consteval { if (n < 0) throw "Negative input!"; } return n <= 1 ? 1 : n * factorial(n - 1); } int main() { std::cout << factorial(5) << '\n'; // OK // std::cout << factorial(-1) << '\n'; // Compile-time error }
5.[[assume]] Attribute
The [[assume]]
attribute allows developers to provide assumptions to the optimizer, improving performance by informing the compiler about invariants.
Example:
#include <iostream> int main() { int x = 5; [[assume(x > 0)]]; std::cout << "x is positive.\n"; }
These features have significantly enhanced C++’s expressiveness, safety, and performance, keeping it a robust choice for modern software development. But how many of them have you already used?