Modern C++: Complete Guide with C++11 to C++23 Features
Master Modern C++ with comprehensive examples: C++11, C++14, C++17, C++20, and C++23 features, modern syntax, best practices, and performance improvements.
Auto & Lambdas
Generic Lambdas
Structured Binding
Concepts & Ranges
Modules
Smart Pointers
Introduction to Modern C++
Modern C++ refers to C++ standards from C++11 onward, which introduced revolutionary features that transformed how C++ is written. These features make code safer, more expressive, and more efficient.
Why Modern C++?
- Safer code with smart pointers
- More expressive syntax
- Better performance with move semantics
- Reduced boilerplate code
- Improved type safety
- Enhanced template programming
C++ Standards Timeline
- C++98: First standardized version
- C++11: Major update (Modern C++ begins)
- C++14: Minor improvements
- C++17: Significant new features
- C++20: Major update (Concepts, Ranges)
- C++23: Latest standard (Modules)
C++11 2011
Auto type deduction, lambda expressions, smart pointers, move semantics, range-based for loops
C++14 2014
Generic lambdas, variable templates, digit separators, return type deduction
C++17 2017
Structured bindings, if constexpr, fold expressions, filesystem library
C++20 2020
Concepts, ranges, coroutines, modules, three-way comparison
C++23 2023
std::expected, std::print, deducing this, stacktrace library
1. C++11: The Modern C++ Revolution
C++11 was a monumental update that fundamentally changed how C++ is written. It introduced features that make C++ safer, more expressive, and more efficient.
Key C++11 Features
// Auto type deduction
auto x = 5; // x is int
auto y = 3.14; // y is double
auto z = "hello"; // z is const char*
// Range-based for loops
for (auto& item : container) { }
// Lambda expressions
auto lambda = [](int x) { return x * x; };
// Smart pointers
auto ptr = std::make_unique(42);
auto shared = std::make_shared(100);
// Move semantics
std::vector v1 = {1, 2, 3};
std::vector v2 = std::move(v1); // Move, not copy
#include <iostream>
#include <vector>
#include <memory>
#include <algorithm>
#include <string>
#include <type_traits>
#include <initializer_list>
#include <utility>
using namespace std;
// 1. Auto Type Deduction
void demonstrateAuto() {
cout << "=== Auto Type Deduction ===" << endl;
// Basic auto usage
auto i = 42; // int
auto d = 3.14159; // double
auto s = "Hello, Modern C++"; // const char*
auto v = vector<int>{1, 2, 3}; // vector<int>
// With references
int x = 100;
auto& ref = x; // int&
ref = 200;
cout << "x after auto ref: " << x << endl;
// With const
const int ci = 300;
auto aci = ci; // int (const is dropped)
const auto caci = ci; // const int
// Auto in function return type (C++14, but demonstrating here)
auto add = [](auto a, auto b) { return a + b; };
cout << "add(5, 3.5): " << add(5, 3.5) << endl;
// Auto with structured binding (C++17, but included for completeness)
// pair<int, string> p = {1, "one"};
// auto [id, name] = p;
}
// 2. Range-based For Loops
void demonstrateRangeFor() {
cout << "\n=== Range-based For Loops ===" << endl;
vector<int> numbers = {1, 2, 3, 4, 5};
// Read-only
cout << "Numbers: ";
for (const auto& num : numbers) {
cout << num << " ";
}
cout << endl;
// Modify elements
cout << "Squared numbers: ";
for (auto& num : numbers) {
num *= num;
cout << num << " ";
}
cout << endl;
// With initializer list
cout << "Chars: ";
for (char c : {'a', 'b', 'c', 'd'}) {
cout << c << " ";
}
cout << endl;
}
// 3. Lambda Expressions
void demonstrateLambdas() {
cout << "\n=== Lambda Expressions ===" << endl;
// Basic lambda
auto greet = []() {
cout << "Hello from lambda!" << endl;
};
greet();
// Lambda with parameters
auto add = [](int a, int b) {
return a + b;
};
cout << "5 + 3 = " << add(5, 3) << endl;
// Lambda with capture clause
int x = 10;
int y = 20;
// Capture by value
auto captureByValue = [x, y]() {
cout << "Captured by value: x=" << x << ", y=" << y << endl;
};
captureByValue();
// Capture by reference
auto captureByRef = [&x, &y]() {
x *= 2;
y *= 3;
cout << "Modified: x=" << x << ", y=" << y << endl;
};
captureByRef();
cout << "After lambda: x=" << x << ", y=" << y << endl;
// Capture all by value/reference
auto captureAllValue = [=]() { return x + y; };
auto captureAllRef = [&]() { x++; y++; };
// Lambda as predicate
vector<int> nums = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
// Count even numbers
int evenCount = count_if(nums.begin(), nums.end(),
[](int n) { return n % 2 == 0; });
cout << "Even numbers: " << evenCount << endl;
// Find numbers greater than 5
auto it = find_if(nums.begin(), nums.end(),
[](int n) { return n > 5; });
if (it != nums.end()) {
cout << "First number > 5: " << *it << endl;
}
// Lambda with explicit return type
auto divide = [](double a, double b) -> double {
if (b == 0) return 0;
return a / b;
};
cout << "10 / 3 = " << divide(10, 3) << endl;
}
// 4. Smart Pointers
void demonstrateSmartPointers() {
cout << "\n=== Smart Pointers ===" << endl;
// Unique pointer (exclusive ownership)
{
auto uniquePtr = make_unique<int>(42);
cout << "Unique ptr value: " << *uniquePtr << endl;
// Can't copy unique_ptr, but can move
auto movedPtr = move(uniquePtr);
if (!uniquePtr) {
cout << "Original unique_ptr is now null" << endl;
}
// Automatically deleted when out of scope
}
// Shared pointer (shared ownership)
{
auto shared1 = make_shared<string>("Shared String");
{
auto shared2 = shared1; // Reference count: 2
auto shared3 = shared2; // Reference count: 3
cout << "Shared string: " << *shared1
<< ", use count: " << shared1.use_count() << endl;
} // shared2 and shared3 destroyed, count back to 1
cout << "After scope, use count: " << shared1.use_count() << endl;
} // shared1 destroyed, string deleted
// Weak pointer (non-owning reference)
{
auto shared = make_shared<int>(100);
weak_ptr<int> weak = shared;
cout << "\nWeak pointer demonstration:" << endl;
cout << "Shared use count: " << shared.use_count() << endl;
if (auto locked = weak.lock()) {
cout << "Weak pointer locked successfully: " << *locked << endl;
} else {
cout << "Weak pointer expired" << endl;
}
// Reset shared pointer
shared.reset();
if (weak.expired()) {
cout << "Weak pointer expired after shared reset" << endl;
}
}
// Custom deleter
{
auto fileDeleter = [](FILE* f) {
if (f) {
fclose(f);
cout << "File closed by custom deleter" << endl;
}
};
unique_ptr<FILE, decltype(fileDeleter)>
filePtr(fopen("test.txt", "w"), fileDeleter);
if (filePtr) {
fprintf(filePtr.get(), "Test data");
cout << "File written" << endl;
}
// File automatically closed by deleter
}
}
// 5. Move Semantics and Rvalue References
class Resource {
int* data;
size_t size;
public:
Resource(size_t s) : size(s), data(new int[s]) {
cout << "Resource allocated: " << size << " elements" << endl;
}
// Copy constructor
Resource(const Resource& other) : size(other.size), data(new int[other.size]) {
copy(other.data, other.data + size, data);
cout << "Resource copied" << endl;
}
// Move constructor
Resource(Resource&& other) noexcept
: size(other.size), data(other.data) {
other.data = nullptr;
other.size = 0;
cout << "Resource moved" << endl;
}
// Copy assignment
Resource& operator=(const Resource& other) {
if (this != &other) {
delete[] data;
size = other.size;
data = new int[size];
copy(other.data, other.data + size, data);
cout << "Resource copy assigned" << endl;
}
return *this;
}
// Move assignment
Resource& operator=(Resource&& other) noexcept {
if (this != &other) {
delete[] data;
data = other.data;
size = other.size;
other.data = nullptr;
other.size = 0;
cout << "Resource move assigned" << endl;
}
return *this;
}
~Resource() {
delete[] data;
cout << "Resource destroyed" << endl;
}
void fill(int value) {
fill_n(data, size, value);
}
};
void demonstrateMoveSemantics() {
cout << "\n=== Move Semantics ===" << endl;
// Create resource
Resource res1(1000000);
res1.fill(42);
// Move instead of copy
Resource res2 = move(res1); // Calls move constructor
// Use std::move to enable moving
vector<Resource> resources;
resources.reserve(10);
cout << "\nAdding resources to vector:" << endl;
resources.push_back(Resource(500000)); // Temporary, moved
resources.push_back(move(res2)); // Existing object, moved
// Perfect forwarding example
auto createResource = [](size_t size) {
return Resource(size);
};
auto res3 = createResource(1000); // RVO or move
}
// 6. nullptr and Strongly-typed Enums
void demonstrateNullptrAndEnums() {
cout << "\n=== nullptr and Strongly-typed Enums ===" << endl;
// nullptr instead of NULL
int* ptr1 = nullptr;
double* ptr2 = nullptr;
if (ptr1 == nullptr) {
cout << "Pointer is null (using nullptr)" << endl;
}
// Old enum (C++98)
enum ColorOld { RED, GREEN, BLUE };
ColorOld oldColor = RED;
// New enum class (C++11)
enum class ColorNew { Red, Green, Blue };
ColorNew newColor = ColorNew::Red;
enum class TrafficLight : char { Red = 'R', Yellow = 'Y', Green = 'G' };
TrafficLight light = TrafficLight::Green;
cout << "Old enum value: " << oldColor << endl;
// cout << newColor << endl; // Error: no implicit conversion
cout << "New enum value: " << static_cast<int>(newColor) << endl;
}
// 7. Type Traits and Static Assert
void demonstrateTypeTraits() {
cout << "\n=== Type Traits and Static Assert ===" << endl;
// Type traits
cout << boolalpha;
cout << "is_integral<int>: " << is_integral<int>::value << endl;
cout << "is_floating_point<double>: " << is_floating_point<double>::value << endl;
cout << "is_pointer<int*>: " << is_pointer<int*>::value << endl;
cout << "is_reference<int&>: " << is_reference<int&>::value << endl;
// Static assert (compile-time check)
static_assert(sizeof(int) >= 4, "int must be at least 4 bytes");
static_assert(is_integral<int>::value, "int must be integral type");
// Enable_if for SFINAE
template<typename T>
typename enable_if<is_integral<T>::value, T>::type
square(T x) {
return x * x;
}
template<typename T>
typename enable_if<is_floating_point<T>::value, T>::type
square(T x) {
return x * x;
}
cout << "square(5): " << square(5) << endl;
cout << "square(3.14): " << square(3.14) << endl;
// square("hello"); // Error: no matching function
}
// 8. Variadic Templates
template<typename T>
void print(T t) {
cout << t << endl;
}
template<typename T, typename... Args>
void print(T t, Args... args) {
cout << t << " ";
print(args...);
}
template<typename... Args>
auto sum(Args... args) {
return (args + ...); // Fold expression (C++17)
}
void demonstrateVariadicTemplates() {
cout << "\n=== Variadic Templates ===" << endl;
print(1, 2.5, "hello", 'a');
cout << "Sum: " << sum(1, 2, 3, 4, 5) << endl;
}
int main() {
cout << "=== C++11 FEATURES DEMONSTRATION ===" << endl << endl;
demonstrateAuto();
demonstrateRangeFor();
demonstrateLambdas();
demonstrateSmartPointers();
demonstrateMoveSemantics();
demonstrateNullptrAndEnums();
demonstrateTypeTraits();
demonstrateVariadicTemplates();
cout << "\n=== END OF C++11 DEMONSTRATION ===" << endl;
return 0;
}
- Use
autowhen type is obvious from context - Prefer smart pointers over raw pointers
- Use
nullptrinstead ofNULL - Implement move semantics for resource-owning classes
- Use
enum classfor type-safe enumerations - Prefer range-based for loops for container iteration
- Using
autowhen type isn't clear - Capturing everything by reference in lambdas
- Not marking move operations as
noexcept - Forgetting to implement move constructor/assignment
- Using
shared_ptrwhenunique_ptrsuffices - Not checking if
weak_ptrhas expired
C++14 2. C++14: Refinements and Improvements
C++14 built upon C++11 with smaller but significant improvements that made the language more convenient and powerful.
Key C++14 Features
// Generic lambdas
auto lambda = [](auto x, auto y) { return x + y; };
// Return type deduction for normal functions
auto add(int a, int b) {
return a + b; // return type deduced as int
}
// Binary literals and digit separators
int binary = 0b1010'1010;
int large = 1'000'000'000;
// Variable templates
template<typename T>
constexpr T pi = T(3.1415926535897932385L);
// std::make_unique (actually C++14, not C++11)
auto ptr = std::make_unique<int>(42);
#include <iostream>
#include <memory>
#include <type_traits>
#include <complex>
#include <chrono>
#include <tuple>
#include <algorithm>
#include <vector>
using namespace std;
// 1. Generic Lambdas
void demonstrateGenericLambdas() {
cout << "=== Generic Lambdas ===" << endl;
// Generic lambda with auto parameters
auto add = [](auto a, auto b) {
return a + b;
};
cout << "add(5, 3): " << add(5, 3) << endl;
cout << "add(2.5, 3.7): " << add(2.5, 3.7) << endl;
cout << "add(string(\"Hello, \"), string(\"World!\")): "
<< add(string("Hello, "), string("World!")) << endl;
// Generic lambda in algorithm
vector<int> numbers = {1, 2, 3, 4, 5};
vector<double> doubles = {1.1, 2.2, 3.3, 4.4, 5.5};
// Same lambda works with different types
auto print = [](const auto& container) {
for (const auto& item : container) {
cout << item << " ";
}
cout << endl;
};
cout << "Numbers: ";
print(numbers);
cout << "Doubles: ";
print(doubles);
// Generic lambda with capture
int multiplier = 2;
auto scale = [multiplier](auto x) {
return x * multiplier;
};
cout << "scale(5): " << scale(5) << endl;
cout << "scale(3.14): " << scale(3.14) << endl;
}
// 2. Return Type Deduction for Normal Functions
auto add(int a, int b) {
return a + b; // Deduced as int
}
auto getPi() {
return 3.141592653589793; // Deduced as double
}
auto createVector() {
return vector<int>{1, 2, 3, 4, 5}; // Deduced as vector<int>
}
template<typename T, typename U>
auto multiply(T a, U b) -> decltype(a * b) {
return a * b;
}
void demonstrateReturnTypeDeduction() {
cout << "\n=== Return Type Deduction ===" << endl;
cout << "add(10, 20): " << add(10, 20) << endl;
cout << "getPi(): " << getPi() << endl;
auto vec = createVector();
cout << "Vector size: " << vec.size() << endl;
cout << "multiply(5, 3.5): " << multiply(5, 3.5) << endl;
cout << "multiply(2, 3): " << multiply(2, 3) << endl;
}
// 3. Binary Literals and Digit Separators
void demonstrateLiterals() {
cout << "\n=== Binary Literals and Digit Separators ===" << endl;
// Binary literals
int binary = 0b10101010;
int binaryWithSeparator = 0b1010'1010;
cout << "Binary 0b10101010: " << binary << endl;
cout << "Binary 0b1010'1010: " << binaryWithSeparator << endl;
cout << "Binary in hex: 0x" << hex << binary << dec << endl;
// Digit separators for readability
long long bigNumber = 1'000'000'000;
double pi = 3.141'592'653'589'793;
long long creditCard = 1234'5678'9012'3456;
long long bytes = 0xFF'FF'FF'FF;
cout << "Big number: " << bigNumber << endl;
cout << "Pi: " << pi << endl;
cout << "Credit card (example): " << creditCard << endl;
cout << "Max 32-bit: " << bytes << endl;
}
// 4. Variable Templates
template<typename T>
constexpr T pi = T(3.1415926535897932385L);
template<typename T>
constexpr T e = T(2.7182818284590452353L);
template<typename T>
constexpr T epsilon = T(1e-6);
void demonstrateVariableTemplates() {
cout << "\n=== Variable Templates ===" << endl;
cout << "Pi as float: " << pi<float> << endl;
cout << "Pi as double: " << pi<double> << endl;
cout << "Pi as long double: " << pi<long double> << endl;
cout << "E as float: " << e<float> << endl;
cout << "E as double: " << e<double> << endl;
cout << "Epsilon for float: " << epsilon<float> << endl;
cout << "Epsilon for double: " << epsilon<double> << endl;
}
// 5. constexpr improvements
constexpr int factorial(int n) {
int result = 1;
for (int i = 1; i <= n; ++i) {
result *= i;
}
return result;
}
constexpr int power(int base, int exp) {
int result = 1;
for (int i = 0; i < exp; ++i) {
result *= base;
}
return result;
}
void demonstrateConstexprImprovements() {
cout << "\n=== constexpr Improvements ===" << endl;
// Can now use loops and variables in constexpr functions
constexpr int fact5 = factorial(5);
constexpr int pow2_10 = power(2, 10);
cout << "5! = " << fact5 << endl;
cout << "2^10 = " << pow2_10 << endl;
// Can be used at compile time
int array1[factorial(5)]; // OK in C++14
int array2[power(2, 3)]; // OK in C++14
cout << "Size of array1: " << sizeof(array1) / sizeof(array1[0]) << endl;
cout << "Size of array2: " << sizeof(array2) / sizeof(array2[0]) << endl;
}
// 6. std::make_unique (actually C++14)
class Widget {
int id;
public:
Widget(int i) : id(i) {
cout << "Widget " << id << " created" << endl;
}
~Widget() {
cout << "Widget " << id << " destroyed" << endl;
}
void doSomething() const {
cout << "Widget " << id << " doing something" << endl;
}
};
void demonstrateMakeUnique() {
cout << "\n=== std::make_unique ===" << endl;
// C++11 way (before make_unique)
// unique_ptr<Widget> ptr1(new Widget(1));
// C++14 way (with make_unique)
auto ptr1 = make_unique<Widget>(1);
auto ptr2 = make_unique<Widget>(2);
ptr1->doSomething();
ptr2->doSomething();
// Array version
auto arrayPtr = make_unique<Widget[]>(3);
// With custom deleter
auto customPtr = unique_ptr<Widget, void(*)(Widget*)>(
new Widget(99),
[](Widget* w) {
cout << "Custom deleter called" << endl;
delete w;
}
);
}
// 7. Heterogeneous Lookup in Associative Containers
struct StringCompare {
using is_transparent = void;
bool operator()(const string& a, const string& b) const {
return a < b;
}
bool operator()(const string& a, const char* b) const {
return a < b;
}
bool operator()(const char* a, const string& b) const {
return a < b;
}
};
void demonstrateHeterogeneousLookup() {
cout << "\n=== Heterogeneous Lookup ===" << endl;
// Set with transparent comparator
set<string, StringCompare> words = {"apple", "banana", "cherry"};
// Can find with string or const char*
auto it1 = words.find("banana");
if (it1 != words.end()) {
cout << "Found with const char*: " << *it1 << endl;
}
string key = "apple";
auto it2 = words.find(key);
if (it2 != words.end()) {
cout << "Found with string: " << *it2 << endl;
}
// Also works with map
map<string, int, StringCompare> wordCount = {
{"apple", 5},
{"banana", 3},
{"cherry", 8}
};
auto it3 = wordCount.find("cherry");
if (it3 != wordCount.end()) {
cout << "Cherry count: " << it3->second << endl;
}
}
// 8. std::integer_sequence
template<typename T, T... Ints>
void print_sequence(integer_sequence<T, Ints...>) {
((cout << Ints << " "), ...);
cout << endl;
}
template<typename T, T N>
using make_integer_sequence = std::make_integer_sequence<T, N>;
void demonstrateIntegerSequence() {
cout << "\n=== std::integer_sequence ===" << endl;
print_sequence(integer_sequence<int, 1, 2, 3, 4, 5>{});
print_sequence(make_integer_sequence<int, 10>{});
// Useful for template metaprogramming
auto seq = make_index_sequence<5>{};
cout << "Index sequence: ";
print_sequence(seq);
}
int main() {
cout << "=== C++14 FEATURES DEMONSTRATION ===" << endl << endl;
demonstrateGenericLambdas();
demonstrateReturnTypeDeduction();
demonstrateLiterals();
demonstrateVariableTemplates();
demonstrateConstexprImprovements();
demonstrateMakeUnique();
demonstrateHeterogeneousLookup();
demonstrateIntegerSequence();
cout << "\n=== END OF C++14 DEMONSTRATION ===" << endl;
return 0;
}
C++14 Impact
- Generic lambdas reduce code duplication
- Return type deduction simplifies function definitions
- Digit separators improve code readability
- Variable templates enable more generic code
- Extended constexpr allows more compile-time computation
- std::make_unique completes smart pointer utilities
C++17 3. C++17: Major Feature Additions
C++17 added significant features that make C++ more expressive, safe, and convenient for modern programming.
Structured Bindings
std::pair<int, std::string> p{1, "one"};
auto [id, name] = p;
// id = 1, name = "one"
std::map<int, std::string> m{{1, "one"}, {2, "two"}};
for (const auto& [key, value] : m) {
// Use key and value
}
if constexpr
template<typename T>
void process(T value) {
if constexpr (is_integral_v<T>) {
// Compile-time branch
cout << "Integer: " << value;
} else if constexpr (is_floating_point_v<T>) {
cout << "Float: " << value;
} else {
cout << "Other: " << value;
}
}
Fold Expressions
template<typename... Args>
auto sum(Args... args) {
return (... + args); // Fold expression
}
template<typename... Args>
bool allTrue(Args... args) {
return (... && args); // Logical AND fold
}
#include <iostream>
#include <tuple>
#include <variant>
#include <optional>
#include <string>
#include <vector>
#include <map>
#include <algorithm>
#include <filesystem>
#include <any>
#include <type_traits>
#include <numeric>
using namespace std;
namespace fs = filesystem;
// 1. Structured Bindings
void demonstrateStructuredBindings() {
cout << "=== Structured Bindings ===" << endl;
// With pair
pair<int, string> p{42, "answer"};
auto [num, str] = p;
cout << "Pair: " << num << ", " << str << endl;
// With tuple
tuple<int, string, double> t{1, "pi", 3.14};
auto [id, name, value] = t;
cout << "Tuple: " << id << ", " << name << ", " << value << endl;
// With array
int arr[] = {10, 20, 30};
auto [a, b, c] = arr;
cout << "Array: " << a << ", " << b << ", " << c << endl;
// With struct
struct Point { int x; int y; string name; };
Point point{100, 200, "origin"};
auto [x, y, name] = point;
cout << "Point: " << x << ", " << y << ", " << name << endl;
// In range-based for loops with maps
map<int, string> m{{1, "one"}, {2, "two"}, {3, "three"}};
cout << "Map contents:" << endl;
for (const auto& [key, value] : m) {
cout << " " << key << ": " << value << endl;
}
// With references
auto& [refNum, refStr] = p;
refNum = 100;
refStr = "modified";
cout << "Modified pair: " << p.first << ", " << p.second << endl;
}
// 2. if constexpr
template<typename T>
void processValue(T value) {
if constexpr (is_integral_v<T>) {
cout << "Integer processing: " << value * 2 << endl;
} else if constexpr (is_floating_point_v<T>) {
cout << "Float processing: " << value + 1.0 << endl;
} else if constexpr (is_same_v<T, string>) {
cout << "String processing: " << value.length() << " chars" << endl;
} else {
cout << "Generic processing: " << value << endl;
}
}
template<typename T>
auto getSize(const T& container) {
if constexpr (has_size<T>::value) {
return container.size();
} else {
return sizeof(container);
}
}
void demonstrateIfConstexpr() {
cout << "\n=== if constexpr ===" << endl;
processValue(42);
processValue(3.14);
processValue(string("hello"));
// Different code paths compiled based on type
vector<int> v{1, 2, 3};
cout << "Vector size: " << getSize(v) << endl;
int x = 100;
cout << "Int size: " << getSize(x) << endl;
}
// 3. Fold Expressions
template<typename... Args>
auto sum(Args... args) {
return (... + args); // Left fold: ((arg1 + arg2) + arg3) + ...
}
template<typename... Args>
auto sumRight(Args... args) {
return (args + ...); // Right fold: arg1 + (arg2 + (arg3 + ...))
}
template<typename... Args>
bool allTrue(Args... args) {
return (... && args);
}
template<typename... Args>
bool anyTrue(Args... args) {
return (... || args);
}
template<typename T, typename... Args>
void printAll(T first, Args... args) {
cout << first;
((cout << ", " << args), ...);
cout << endl;
}
void demonstrateFoldExpressions() {
cout << "\n=== Fold Expressions ===" << endl;
cout << "Sum: " << sum(1, 2, 3, 4, 5) << endl;
cout << "Sum with doubles: " << sum(1.1, 2.2, 3.3) << endl;
cout << "Sum with strings: " << sum(string("a"), string("b"), string("c")) << endl;
cout << boolalpha;
cout << "allTrue(true, true, true): " << allTrue(true, true, true) << endl;
cout << "allTrue(true, false, true): " << allTrue(true, false, true) << endl;
cout << "anyTrue(false, false, true): " << anyTrue(false, false, true) << endl;
cout << "anyTrue(false, false, false): " << anyTrue(false, false, false) << endl;
cout << "Printing: ";
printAll(1, 2.5, "three", '4');
}
// 4. std::optional
optional<double> safeDivide(double a, double b) {
if (b == 0) {
return nullopt; // No value
}
return a / b;
}
optional<string> findName(const map<int, string>& m, int key) {
if (auto it = m.find(key); it != m.end()) {
return it->second;
}
return nullopt;
}
void demonstrateOptional() {
cout << "\n=== std::optional ===" << endl;
auto result1 = safeDivide(10.0, 2.0);
auto result2 = safeDivide(10.0, 0.0);
if (result1) {
cout << "Division successful: " << *result1 << endl;
}
if (!result2) {
cout << "Division by zero failed" << endl;
}
// Using value_or for default
cout << "Result1 or default: " << result1.value_or(0.0) << endl;
cout << "Result2 or default: " << result2.value_or(0.0) << endl;
// With structured binding
map<int, string> names{{1, "Alice"}, {2, "Bob"}};
if (auto name = findName(names, 1)) {
cout << "Found: " << *name << endl;
}
if (auto name = findName(names, 99)) {
cout << "Found: " << *name << endl;
} else {
cout << "Not found" << endl;
}
}
// 5. std::variant
using Number = variant<int, double, string>;
void printNumber(const Number& num) {
visit([](auto& value) {
using T = decay_t<decltype(value)>;
if constexpr (is_same_v<T, int>) {
cout << "Integer: " << value << endl;
} else if constexpr (is_same_v<T, double>) {
cout << "Double: " << value << endl;
} else if constexpr (is_same_v<T, string>) {
cout << "String: " << value << endl;
}
}, num);
}
void demonstrateVariant() {
cout << "\n=== std::variant ===" << endl;
variant<int, double, string> v;
v = 42;
printNumber(v);
v = 3.14;
printNumber(v);
v = "hello";
printNumber(v);
// Check what's currently stored
if (holds_alternative<int>(v)) {
cout << "Currently holds int" << endl;
} else if (holds_alternative<string>(v)) {
cout << "Currently holds string" << endl;
}
// Get the value (throws bad_variant_access if wrong type)
try {
auto str = get<string>(v);
cout << "Got string: " << str << endl;
// This will throw
auto dbl = get<double>(v);
} catch (const bad_variant_access& e) {
cout << "Exception: " << e.what() << endl;
}
// Get by index
cout << "Index: " << v.index() << endl;
}
// 6. std::any
void demonstrateAny() {
cout << "\n=== std::any ===" << endl;
any a = 42;
cout << "Type: " << a.type().name() << endl;
cout << "Value: " << any_cast<int>(a) << endl;
a = string("hello");
cout << "Type: " << a.type().name() << endl;
cout << "Value: " << any_cast<string>(a) << endl;
a = 3.14;
// Check type
if (a.has_value()) {
cout << "Has value of type: " << a.type().name() << endl;
}
// Reset
a.reset();
if (!a.has_value()) {
cout << "No value after reset" << endl;
}
// Safe any_cast
a = 100;
if (auto ptr = any_cast<int>(&a)) {
cout << "Pointer to int: " << *ptr << endl;
}
if (auto ptr = any_cast<double>(&a)) {
cout << "Pointer to double: " << *ptr << endl;
} else {
cout << "Not a double" << endl;
}
}
// 7. Filesystem Library
void demonstrateFilesystem() {
cout << "\n=== Filesystem Library ===" << endl;
// Create directory
fs::create_directory("test_dir");
// Create file
ofstream("test_dir/test.txt") << "Test content";
// Check if exists
if (fs::exists("test_dir/test.txt")) {
cout << "File exists" << endl;
}
// Get file size
if (auto size = fs::file_size("test_dir/test.txt"); size != static_cast<uintmax_t>(-1)) {
cout << "File size: " << size << " bytes" << endl;
}
// Iterate directory
cout << "Directory contents:" << endl;
for (const auto& entry : fs::directory_iterator("test_dir")) {
cout << " " << entry.path().filename() << endl;
}
// Get current path
cout << "Current path: " << fs::current_path() << endl;
// Cleanup
fs::remove_all("test_dir");
}
// 8. Parallel Algorithms
void demonstrateParallelAlgorithms() {
cout << "\n=== Parallel Algorithms ===" << endl;
vector<int> data(1000000);
iota(data.begin(), data.end(), 0);
// Sequential sort
auto seq_data = data;
auto start = chrono::high_resolution_clock::now();
sort(seq_data.begin(), seq_data.end());
auto end = chrono::high_resolution_clock::now();
auto seq_time = chrono::duration_cast<chrono::milliseconds>(end - start);
// Parallel sort (if supported)
auto par_data = data;
start = chrono::high_resolution_clock::now();
sort(execution::par, par_data.begin(), par_data.end());
end = chrono::high_resolution_clock::now();
auto par_time = chrono::duration_cast<chrono::milliseconds>(end - start);
cout << "Sequential sort time: " << seq_time.count() << "ms" << endl;
cout << "Parallel sort time: " << par_time.count() << "ms" << endl;
// Other parallel algorithms
vector<int> numbers(1000);
iota(numbers.begin(), numbers.end(), 1);
// Parallel transform
vector<int> squares(numbers.size());
transform(execution::par, numbers.begin(), numbers.end(),
squares.begin(), [](int n) { return n * n; });
// Parallel reduce
auto sum = reduce(execution::par, numbers.begin(), numbers.end());
cout << "Sum of 1..1000: " << sum << endl;
}
// 9. Inline Variables
// Header file would have:
// inline constexpr double pi = 3.141592653589793;
// inline vector<string> defaultNames = {"Alice", "Bob", "Charlie"};
inline constexpr double inline_pi = 3.141592653589793;
class MyClass {
public:
inline static int counter = 0; // Inline static member
MyClass() { counter++; }
};
void demonstrateInlineVariables() {
cout << "\n=== Inline Variables ===" << endl;
cout << "Inline pi: " << inline_pi << endl;
MyClass obj1, obj2, obj3;
cout << "Objects created: " << MyClass::counter << endl;
}
int main() {
cout << "=== C++17 FEATURES DEMONSTRATION ===" << endl << endl;
demonstrateStructuredBindings();
demonstrateIfConstexpr();
demonstrateFoldExpressions();
demonstrateOptional();
demonstrateVariant();
demonstrateAny();
demonstrateFilesystem();
demonstrateParallelAlgorithms();
demonstrateInlineVariables();
cout << "\n=== END OF C++17 DEMONSTRATION ===" << endl;
return 0;
}
C++11 Type Safety
- Smart pointers
- nullptr
- enum class
- type traits
- Better but limited
C++17 Type Safety
- std::optional
- std::variant
- std::any
- Structured bindings
- Much more comprehensive
C++20 4. C++20: The Next Major Revolution
C++20 introduced game-changing features that fundamentally transform template programming, ranges, concurrency, and module systems.
Key C++20 Features
// Concepts
template<typename T>
concept Integral = is_integral_v<T>;
template<Integral T>
T square(T x) { return x * x; }
// Ranges
vector<int> numbers{1, 2, 3, 4, 5};
auto even = numbers | views::filter([](int n){ return n % 2 == 0; });
// Coroutines
generator<int> range(int start, int end) {
for (int i = start; i < end; ++i)
co_yield i;
}
// Three-way comparison (spaceship operator)
auto operator<=>(const Point&) const = default;
#include <iostream>
#include <concepts>
#include <ranges>
#include <vector>
#include <algorithm>
#include <compare>
#include <coroutine>
#include <format>
#include <span>
#include <bit>
#include <numbers>
#include <chrono>
#include <source_location>
using namespace std;
// 1. Concepts
template<typename T>
concept Arithmetic = is_arithmetic_v<T>;
template<typename T>
concept Addable = requires(T a, T b) {
{ a + b } -> convertible_to<T>;
};
template<typename T>
concept Container = requires(T t) {
typename T::value_type;
typename T::iterator;
typename T::const_iterator;
{ t.begin() } -> same_as<typename T::iterator>;
{ t.end() } -> same_as<typename T::iterator>;
{ t.size() } -> integral;
};
// Using concepts
template<Arithmetic T>
T square(T x) {
return x * x;
}
template<Addable T>
T addTwice(T a, T b) {
return a + a + b + b;
}
template<Container C>
void printContainer(const C& container) {
for (const auto& item : container) {
cout << item << " ";
}
cout << endl;
}
// Requires clause
template<typename T>
requires integral<T> || floating_point<T>
T absolute(T value) {
return value < 0 ? -value : value;
}
void demonstrateConcepts() {
cout << "=== Concepts ===" << endl;
cout << "square(5): " << square(5) << endl;
cout << "square(3.14): " << square(3.14) << endl;
// square("hello"); // Error: doesn't satisfy Arithmetic
cout << "addTwice(10, 20): " << addTwice(10, 20) << endl;
cout << "addTwice(string(\"a\"), string(\"b\")): "
<< addTwice(string("a"), string("b")) << endl;
vector<int> v{1, 2, 3, 4, 5};
printContainer(v);
cout << "absolute(-5): " << absolute(-5) << endl;
cout << "absolute(-3.14): " << absolute(-3.14) << endl;
}
// 2. Ranges
void demonstrateRanges() {
cout << "\n=== Ranges ===" << endl;
vector<int> numbers{1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
// Filter even numbers
auto even_numbers = numbers | views::filter([](int n) { return n % 2 == 0; });
cout << "Even numbers: ";
for (int n : even_numbers) {
cout << n << " ";
}
cout << endl;
// Transform (square)
auto squares = numbers | views::transform([](int n) { return n * n; });
cout << "Squares: ";
for (int n : squares) {
cout << n << " ";
}
cout << endl;
// Take first 5
auto first5 = numbers | views::take(5);
cout << "First 5: ";
ranges::copy(first5, ostream_iterator<int>(cout, " "));
cout << endl;
// Drop first 3
auto after3 = numbers | views::drop(3);
cout << "After 3: ";
ranges::copy(after3, ostream_iterator<int>(cout, " "));
cout << endl;
// Reverse
auto reversed = numbers | views::reverse;
cout << "Reversed: ";
ranges::copy(reversed, ostream_iterator<int>(cout, " "));
cout << endl;
// Pipeline: filter even, square them, take first 3
auto pipeline = numbers
| views::filter([](int n) { return n % 2 == 0; })
| views::transform([](int n) { return n * n; })
| views::take(3);
cout << "Pipeline (even squared first 3): ";
for (int n : pipeline) {
cout << n << " ";
}
cout << endl;
// Range algorithms
cout << "Max: " << ranges::max(numbers) << endl;
cout << "Min: " << ranges::min(numbers) << endl;
cout << "Sum: " << ranges::fold_left(numbers, 0, plus<>{}) << endl;
// Range-based sort
vector<int> to_sort{5, 2, 8, 1, 9, 3};
ranges::sort(to_sort);
cout << "Sorted: ";
ranges::copy(to_sort, ostream_iterator<int>(cout, " "));
cout << endl;
}
// 3. Coroutines (simplified example)
#if __has_include(<coroutine>)
#include <coroutine>
generator<int> range(int start, int end) {
for (int i = start; i < end; ++i) {
co_yield i;
}
}
generator<int> fibonacci(int n) {
int a = 0, b = 1;
for (int i = 0; i < n; ++i) {
co_yield a;
int temp = a;
a = b;
b = temp + b;
}
}
void demonstrateCoroutines() {
cout << "\n=== Coroutines ===" << endl;
cout << "Range 1-5: ";
for (int i : range(1, 6)) {
cout << i << " ";
}
cout << endl;
cout << "First 10 Fibonacci numbers: ";
for (int i : fibonacci(10)) {
cout << i << " ";
}
cout << endl;
}
#else
void demonstrateCoroutines() {
cout << "\n=== Coroutines (not supported in this compiler) ===" << endl;
}
#endif
// 4. Three-way Comparison (Spaceship Operator)
class Point {
int x, y;
public:
Point(int x, int y) : x(x), y(y) {}
// Auto-generate all comparison operators
auto operator<=>(const Point& other) const = default;
// Or customize
/*
auto operator<=>(const Point& other) const {
if (auto cmp = x <=> other.x; cmp != 0) return cmp;
return y <=> other.y;
}
*/
friend ostream& operator<<(ostream& os, const Point& p) {
return os << "(" << p.x << ", " << p.y << ")";
}
};
void demonstrateSpaceship() {
cout << "\n=== Three-way Comparison ===" << endl;
Point p1{1, 2};
Point p2{1, 2};
Point p3{2, 1};
Point p4{1, 3};
cout << boolalpha;
cout << p1 << " == " << p2 << ": " << (p1 == p2) << endl;
cout << p1 << " != " << p3 << ": " << (p1 != p3) << endl;
cout << p1 << " < " << p3 << ": " << (p1 < p3) << endl;
cout << p1 << " <= " << p4 << ": " << (p1 <= p4) << endl;
cout << p3 << " > " << p1 << ": " << (p3 > p1) << endl;
cout << p3 << " >= " << p4 << ": " << (p3 >= p4) << endl;
// Direct three-way comparison
auto result = p1 <=> p3;
if (result < 0) cout << "p1 < p3" << endl;
else if (result > 0) cout << "p1 > p3" << endl;
else cout << "p1 == p3" << endl;
}
// 5. std::format
void demonstrateFormat() {
cout << "\n=== std::format ===" << endl;
string formatted;
// Basic formatting
formatted = format("Hello, {}!", "World");
cout << formatted << endl;
// Number formatting
formatted = format("Integer: {}, Float: {:.2f}, Scientific: {:.2e}",
42, 3.14159, 123456.789);
cout << formatted << endl;
// Positional arguments
formatted = format("{1} {0} {1}", "a", "b");
cout << formatted << endl;
// Named arguments (C++20 library extension)
// formatted = format("Name: {name}, Age: {age}",
// arg("name", "Alice"), arg("age", 30));
// Width and alignment
formatted = format("|{:<10}|{:^10}|{:>10}|", "left", "center", "right");
cout << formatted << endl;
// Different bases
formatted = format("Decimal: {}, Hex: {:x}, Binary: {:b}", 255, 255, 255);
cout << formatted << endl;
// Chrono formatting
auto now = chrono::system_clock::now();
formatted = format("Current time: {:%Y-%m-%d %H:%M:%S}", now);
cout << formatted << endl;
}
// 6. std::span
void processSpan(span<const int> data) {
cout << "Span size: " << data.size() << endl;
cout << "Span data: ";
for (int value : data) {
cout << value << " ";
}
cout << endl;
// Subspan
auto sub = data.subspan(1, 3);
cout << "Subspan: ";
for (int value : sub) {
cout << value << " ";
}
cout << endl;
}
void demonstrateSpan() {
cout << "\n=== std::span ===" << endl;
// From array
int arr[] = {1, 2, 3, 4, 5};
processSpan(arr);
// From vector
vector<int> vec{10, 20, 30, 40, 50};
processSpan(vec);
// From initializer list (needs to be stored first)
vector<int> init_vec{100, 200, 300};
processSpan(init_vec);
// Modifying through span
span<int> mutable_span{arr};
mutable_span[0] = 999;
cout << "Modified first element: " << arr[0] << endl;
// Static extent span
span<int, 5> static_span{arr}; // Size known at compile time
cout << "Static span size: " << static_span.size() << endl;
}
// 7. Bit Operations
void demonstrateBitOperations() {
cout << "\n=== Bit Operations ===" << endl;
uint32_t x = 0b10101010;
cout << "x: " << bitset<8>(x) << endl;
cout << "popcount(x): " << popcount(x) << endl;
cout << "has_single_bit(x): " << boolalpha << has_single_bit(x) << endl;
cout << "bit_width(x): " << bit_width(x) << endl;
cout << "rotl(x, 2): " << bitset<8>(rotl(x, 2)) << endl;
cout << "rotr(x, 2): " << bitset<8>(rotr(x, 2)) << endl;
// Endianness
cout << "\nEndianness: ";
if constexpr (endian::native == endian::little) {
cout << "Little endian" << endl;
} else if constexpr (endian::native == endian::big) {
cout << "Big endian" << endl;
} else {
cout << "Mixed endian" << endl;
}
// Bit cast
float f = 3.14f;
auto bits = bit_cast<uint32_t>(f);
cout << "Float bits: " << hex << bits << dec << endl;
}
// 8. Mathematical Constants
void demonstrateMathConstants() {
cout << "\n=== Mathematical Constants ===" << endl;
using namespace numbers;
cout << "pi: " << pi << endl;
cout << "e: " << e << endl;
cout << "sqrt(2): " << sqrt2 << endl;
cout << "sqrt(3): " << sqrt3 << endl;
cout << "phi (golden ratio): " << phi << endl;
cout << "ln(2): " << ln2 << endl;
cout << "ln(10): " << ln10 << endl;
}
// 9. Source Location
void log(const string& message,
const source_location& location = source_location::current()) {
cout << format("[{}:{}:{}] {}",
location.file_name(),
location.line(),
location.column(),
message) << endl;
}
void demonstrateSourceLocation() {
cout << "\n=== Source Location ===" << endl;
log("This is a log message");
auto my_location = source_location::current();
cout << "File: " << my_location.file_name() << endl;
cout << "Line: " << my_location.line() << endl;
cout << "Column: " << my_location.column() << endl;
cout << "Function: " << my_location.function_name() << endl;
}
int main() {
cout << "=== C++20 FEATURES DEMONSTRATION ===" << endl << endl;
demonstrateConcepts();
demonstrateRanges();
demonstrateCoroutines();
demonstrateSpaceship();
demonstrateFormat();
demonstrateSpan();
demonstrateBitOperations();
demonstrateMathConstants();
demonstrateSourceLocation();
cout << "\n=== END OF C++20 DEMONSTRATION ===" << endl;
return 0;
}
- Use concepts to constrain templates for better error messages
- Prefer ranges algorithms over traditional STL algorithms
- Use
std::formatfor type-safe string formatting - Use
std::spanfor non-owning views of contiguous data - Default the spaceship operator when appropriate
- Use coroutines for asynchronous and generator patterns
- Use modules for better compilation times and encapsulation
C++23 5. C++23: The Latest Standard
C++23 continues the evolution with practical improvements, new library features, and enhancements to existing functionality.
Key C++23 Features
// std::expected for error handling
std::expected<int, std::string> parseNumber(std::string_view s);
// std::print for easier output
std::print("Hello, {}!", name);
// Deducing this
struct Widget {
void process(this auto&& self) {
// self is deduced
}
};
// std::stacktrace for debugging
auto trace = std::stacktrace::current();
#include <iostream>
#include <string>
#include <vector>
#include <algorithm>
#include <format>
// Note: Some C++23 features may not be fully implemented in all compilers
// This code demonstrates the concepts even if not compilable everywhere
using namespace std;
// 1. std::expected (Error handling without exceptions)
/*
#include <expected>
enum class ParseError {
InvalidFormat,
OutOfRange,
EmptyString
};
expected<int, ParseError> parseInteger(string_view str) {
if (str.empty()) {
return unexpected{ParseError::EmptyString};
}
int result = 0;
auto [ptr, ec] = from_chars(str.data(), str.data() + str.size(), result);
if (ec == errc::invalid_argument) {
return unexpected{ParseError::InvalidFormat};
}
if (ec == errc::result_out_of_range) {
return unexpected{ParseError::OutOfRange};
}
return result;
}
void demonstrateExpected() {
cout << "=== std::expected ===" << endl;
auto result1 = parseInteger("42");
if (result1) {
cout << "Parsed: " << *result1 << endl;
} else {
cout << "Error: " << static_cast<int>(result1.error()) << endl;
}
auto result2 = parseInteger("abc");
if (!result2) {
cout << "Failed to parse 'abc': "
<< static_cast<int>(result2.error()) << endl;
}
// Transform if valid
auto squared = result1.transform([](int n) { return n * n; });
if (squared) {
cout << "Squared: " << *squared << endl;
}
// Or provide default
auto value = result2.value_or(0);
cout << "Default value: " << value << endl;
}
*/
// 2. std::print (Improved output)
void demonstratePrint() {
cout << "\n=== std::print ===" << endl;
// std::print("Hello, {}!\n", "World");
// std::println("The answer is {}", 42);
// With files
// std::print(stdout, "Writing to stdout\n");
// std::print(stderr, "Error message\n");
// cout << "Note: std::print not available in this compiler" << endl;
cout << "Using std::format as alternative: "
<< format("Hello, {}! The answer is {}\n", "World", 42);
}
// 3. Deducing this (Explicit object parameter)
/*
struct Widget {
int value = 0;
// Traditional member function
void setValue(int v) & {
value = v;
}
// With deducing this
void setValue(this auto&& self, int v) {
self.value = v;
}
// Const-correct automatically
int getValue() const {
return value;
}
// With deducing this
int getValue(this const auto& self) {
return self.value;
}
};
void demonstrateDeducingThis() {
cout << "\n=== Deducing this ===" << endl;
Widget w;
w.setValue(42);
cout << "Value: " << w.getValue() << endl;
const Widget cw{100};
// cw.setValue(200); // Error: const object
cout << "Const value: " << cw.getValue() << endl;
// Works with inheritance
struct Derived : Widget {
using Widget::setValue;
using Widget::getValue;
};
Derived d;
d.setValue(99);
cout << "Derived value: " << d.getValue() << endl;
}
*/
// 4. std::stacktrace (Runtime stack traces)
/*
#include <stacktrace>
void deepFunction(int level) {
if (level > 0) {
deepFunction(level - 1);
} else {
auto trace = stacktrace::current();
cout << "Stack trace (" << trace.size() << " frames):\n";
for (const auto& entry : trace) {
cout << " " << entry.description()
<< " at " << entry.source_file()
<< ":" << entry.source_line() << endl;
}
}
}
void demonstrateStacktrace() {
cout << "\n=== std::stacktrace ===" << endl;
deepFunction(3);
}
*/
// 5. std::mdspan (Multidimensional array view)
/*
#include <mdspan>
void demonstrateMdspan() {
cout << "\n=== std::mdspan ===" << endl;
vector<int> data(24);
iota(data.begin(), data.end(), 0);
// 3D array: 2 x 3 x 4
mdspan mat3d(data.data(), 2, 3, 4);
cout << "3D array (2x3x4):\n";
for (int i = 0; i < mat3d.extent(0); ++i) {
cout << " Layer " << i << ":\n";
for (int j = 0; j < mat3d.extent(1); ++j) {
cout << " ";
for (int k = 0; k < mat3d.extent(2); ++k) {
cout << setw(3) << mat3d[i, j, k] << " ";
}
cout << endl;
}
}
}
*/
// 6. Flat Containers (std::flat_map, std::flat_set)
/*
#include <flat_map>
void demonstrateFlatContainers() {
cout << "\n=== Flat Containers ===" << endl;
flat_map<string, int> ages;
ages.insert({"Alice", 30});
ages.insert({"Bob", 25});
ages.insert({"Charlie", 35});
cout << "Ages:\n";
for (const auto& [name, age] : ages) {
cout << " " << name << ": " << age << endl;
}
// Better cache locality than std::map
// Faster iteration, slower insertion
}
*/
// 7. std::generator (Coroutine-based generators)
/*
#include <generator>
generator<int> range(int start, int stop, int step = 1) {
for (int i = start; i < stop; i += step) {
co_yield i;
}
}
generator<pair<int, int>> enumerate(span<const int> data) {
for (size_t i = 0; i < data.size(); ++i) {
co_yield pair{i, data[i]};
}
}
void demonstrateGenerator() {
cout << "\n=== std::generator ===" << endl;
cout << "Range 1-5: ";
for (int i : range(1, 6)) {
cout << i << " ";
}
cout << endl;
cout << "Even numbers 0-10: ";
for (int i : range(0, 11, 2)) {
cout << i << " ";
}
cout << endl;
vector<int> data{10, 20, 30, 40};
cout << "Enumerated:\n";
for (auto [index, value] : enumerate(data)) {
cout << " [" << index << "] = " << value << endl;
}
}
*/
// 8. std::byteswap (Endian conversion)
void demonstrateByteswap() {
cout << "\n=== std::byteswap ===" << endl;
uint16_t x = 0x1234;
uint32_t y = 0x12345678;
uint64_t z = 0x123456789ABCDEF0;
/*
cout << hex;
cout << "Original x: 0x" << x << endl;
cout << "Byteswapped x: 0x" << byteswap(x) << endl;
cout << "Original y: 0x" << y << endl;
cout << "Byteswapped y: 0x" << byteswap(y) << endl;
cout << "Original z: 0x" << z << endl;
cout << "Byteswapped z: 0x" << byteswap(z) << endl;
cout << dec;
*/
cout << "Note: std::byteswap not available in this compiler" << endl;
}
// 9. New Algorithms
void demonstrateNewAlgorithms() {
cout << "\n=== New Algorithms ===" << endl;
vector<int> data{1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
// std::shift_left, std::shift_right
/*
shift_left(data.begin(), data.end(), 2);
cout << "After shift_left by 2: ";
for (int n : data) cout << n << " ";
cout << endl;
shift_right(data.begin(), data.end(), 3);
cout << "After shift_right by 3: ";
for (int n : data) cout << n << " ";
cout << endl;
*/
// std::starts_with, std::ends_with for strings
string str = "Hello, World!";
/*
cout << boolalpha;
cout << "starts_with \"Hello\": " << str.starts_with("Hello") << endl;
cout << "ends_with \"World!\": " << str.ends_with("World!") << endl;
cout << "contains \", \": " << str.contains(", ") << endl;
*/
cout << "Note: Some C++23 algorithms not available in this compiler" << endl;
}
// 10. Monadic Operations for Optional and Expected
void demonstrateMonadicOperations() {
cout << "\n=== Monadic Operations ===" << endl;
/*
optional<int> opt1 = 42;
optional<int> opt2 = nullopt;
// and_then: chain operations that return optional
auto result1 = opt1.and_then([](int n) -> optional<string> {
if (n > 0) return to_string(n);
return nullopt;
});
// transform: apply function to value if present
auto result2 = opt1.transform([](int n) { return n * 2; });
// or_else: provide alternative if empty
auto result3 = opt2.or_else([] { return optional<int>{100}; });
cout << "opt1 and_then: " << result1.value_or("empty") << endl;
cout << "opt1 transform: " << result2.value_or(0) << endl;
cout << "opt2 or_else: " << result3.value_or(0) << endl;
*/
cout << "Note: Monadic operations not available in this compiler" << endl;
}
int main() {
cout << "=== C++23 FEATURES DEMONSTRATION ===" << endl << endl;
// demonstrateExpected();
demonstratePrint();
// demonstrateDeducingThis();
// demonstrateStacktrace();
// demonstrateMdspan();
// demonstrateFlatContainers();
// demonstrateGenerator();
demonstrateByteswap();
demonstrateNewAlgorithms();
demonstrateMonadicOperations();
cout << "\n=== END OF C++23 DEMONSTRATION ===" << endl;
cout << "Note: Many C++23 features require latest compiler versions" << endl;
return 0;
}
- Many C++23 features are still being implemented in compilers
- Check compiler documentation for feature support
- Use feature test macros to check availability
- Some features may require specific compiler flags
- Consider using the latest stable compiler version
- Production code should verify feature availability
6. Smart Pointers: Modern Memory Management
Smart pointers automate memory management, preventing memory leaks and making code exception-safe. Modern C++ provides three main smart pointers.
std::unique_ptr
Exclusive ownership. Cannot be copied, only moved.
auto ptr = make_unique<T>(args);
auto moved = std::move(ptr);
// ptr is now nullptr
// Custom deleter
auto ptr = unique_ptr<FILE,
decltype(&fclose)>(fopen(...), fclose);
std::shared_ptr
Shared ownership. Reference counted.
auto ptr = make_shared<T>(args);
auto copy = ptr; // Ref count: 2
copy.reset(); // Ref count: 1
// Control block stores ref count
// Atomic operations for thread safety
std::weak_ptr
Non-owning reference to shared_ptr.
weak_ptr<T> weak = shared;
if (auto locked = weak.lock()) {
// Use locked shared_ptr
} else {
// Object was destroyed
}
#include <iostream>
#include <memory>
#include <vector>
#include <string>
#include <functional>
using namespace std;
// 1. Factory function returning unique_ptr
class Product {
string name;
double price;
public:
Product(string n, double p) : name(n), price(p) {}
void display() const {
cout << name << ": $" << price << endl;
}
};
unique_ptr<Product> createProduct(string name, double price) {
return make_unique<Product>(name, price);
}
// 2. Polymorphic hierarchy with smart pointers
class Shape {
public:
virtual void draw() const = 0;
virtual ~Shape() = default;
};
class Circle : public Shape {
double radius;
public:
Circle(double r) : radius(r) {}
void draw() const override {
cout << "Drawing circle with radius " << radius << endl;
}
};
class Rectangle : public Shape {
double width, height;
public:
Rectangle(double w, double h) : width(w), height(h) {}
void draw() const override {
cout << "Drawing rectangle " << width << "x" << height << endl;
}
};
// 3. Observer pattern with weak_ptr to avoid dangling references
class Observer {
public:
virtual void update() = 0;
virtual ~Observer() = default;
};
class Subject {
vector<weak_ptr<Observer>> observers;
void cleanup() {
observers.erase(
remove_if(observers.begin(), observers.end(),
[](const weak_ptr<Observer>& w) {
return w.expired();
}),
observers.end()
);
}
public:
void addObserver(weak_ptr<Observer> observer) {
observers.push_back(observer);
}
void notify() {
cleanup();
for (auto& weak : observers) {
if (auto observer = weak.lock()) {
observer->update();
}
}
}
};
class ConcreteObserver : public Observer {
int id;
public:
ConcreteObserver(int i) : id(i) {}
void update() override {
cout << "Observer " << id << " notified!" << endl;
}
};
// 4. Resource management with custom deleters
struct FileDeleter {
void operator()(FILE* f) const {
if (f) {
fclose(f);
cout << "File closed" << endl;
}
}
};
struct ArrayDeleter {
template<typename T>
void operator()(T* p) const {
delete[] p;
cout << "Array deleted" << endl;
}
};
// 5. Enable shared_from_this for safe shared_ptr creation
class SharedObject : public enable_shared_from_this<SharedObject> {
int data;
public:
SharedObject(int d) : data(d) {
cout << "SharedObject " << data << " created" << endl;
}
~SharedObject() {
cout << "SharedObject " << data << " destroyed" << endl;
}
shared_ptr<SharedObject> getShared() {
return shared_from_this();
}
void process() {
cout << "Processing " << data << endl;
}
};
void demonstrateSmartPointers() {
cout << "=== SMART POINTERS BEST PRACTICES ===" << endl << endl;
// 1. Prefer make_unique/make_shared
cout << "1. Factory functions with make_unique:" << endl;
auto product = createProduct("Laptop", 999.99);
product->display();
// 2. Polymorphic containers
cout << "\n2. Polymorphic containers:" << endl;
vector<unique_ptr<Shape>> shapes;
shapes.push_back(make_unique<Circle>(5.0));
shapes.push_back(make_unique<Rectangle>(4.0, 6.0));
for (const auto& shape : shapes) {
shape->draw();
}
// 3. Observer pattern with weak_ptr
cout << "\n3. Observer pattern with weak_ptr:" << endl;
Subject subject;
{
auto obs1 = make_shared<ConcreteObserver>(1);
auto obs2 = make_shared<ConcreteObserver>(2);
subject.addObserver(obs1);
subject.addObserver(obs2);
cout << "Notifying with observers alive:" << endl;
subject.notify();
}
cout << "\nNotifying after observers destroyed:" << endl;
subject.notify(); // Automatically cleans up expired observers
// 4. Custom deleters
cout << "\n4. Custom deleters:" << endl;
// File with custom deleter
{
unique_ptr<FILE, FileDeleter> file(
fopen("test.txt", "w"),
FileDeleter{}
);
if (file) {
fprintf(file.get(), "Test data");
cout << "File written" << endl;
}
} // File automatically closed
// Array with custom deleter
{
unique_ptr<int[], ArrayDeleter> arr(new int[10]);
for (int i = 0; i < 10; ++i) {
arr[i] = i * 10;
}
cout << "Array created and filled" << endl;
} // Array automatically deleted
// 5. enable_shared_from_this
cout << "\n5. enable_shared_from_this:" << endl;
{
auto obj = make_shared<SharedObject>(42);
auto anotherRef = obj->getShared();
cout << "Use count: " << obj.use_count() << endl;
// Can safely pass shared_from_this to callbacks
function<void()> callback = [obj]() {
obj->process();
};
callback();
}
// 6. Weak_ptr for breaking cycles
cout << "\n6. Breaking cycles with weak_ptr:" << endl;
struct Node {
int value;
shared_ptr<Node> next;
weak_ptr<Node> prev; // Use weak_ptr to break cycle
Node(int v) : value(v) {
cout << "Node " << value << " created" << endl;
}
~Node() {
cout << "Node " << value << " destroyed" << endl;
}
};
{
auto node1 = make_shared<Node>(1);
auto node2 = make_shared<Node>(2);
auto node3 = make_shared<Node>(3);
// Create forward links
node1->next = node2;
node2->next = node3;
// Create backward links with weak_ptr
node2->prev = node1;
node3->prev = node2;
cout << "Nodes created with weak_ptr back links" << endl;
// Traverse forward
cout << "Forward traversal: ";
auto current = node1;
while (current) {
cout << current->value << " ";
current = current->next;
}
cout << endl;
// Traverse backward using weak_ptr
cout << "Backward traversal: ";
current = node3;
while (auto prev = current->prev.lock()) {
cout << prev->value << " ";
current = prev;
}
cout << endl;
} // All nodes properly destroyed (no memory leak)
// 7. Aliasing constructor
cout << "\n7. Aliasing constructor:" << endl;
{
struct Base {
int id;
Base(int i) : id(i) {}
};
struct Derived : Base {
string name;
Derived(int i, string n) : Base(i), name(n) {}
};
auto derived = make_shared<Derived>(1, "Alice");
// Create shared_ptr to base that shares ownership
shared_ptr<Base> base(derived, static_cast<Base*>(derived.get()));
cout << "Derived use count: " << derived.use_count() << endl;
cout << "Base use count: " << base.use_count() << endl;
cout << "Same object? " << (derived.get() == base.get()) << endl;
}
// 8. Performance considerations
cout << "\n8. Performance considerations:" << endl;
// make_shared vs shared_ptr(new T)
{
// make_shared: single allocation for object + control block
auto ptr1 = make_shared<vector<int>>(1000);
// shared_ptr(new T): two allocations
auto ptr2 = shared_ptr<vector<int>>(new vector<int>(1000));
cout << "make_shared: more efficient (one allocation)" << endl;
cout << "shared_ptr(new T): less efficient (two allocations)" << endl;
}
cout << "\n=== END OF SMART POINTERS DEMONSTRATION ===" << endl;
}
int main() {
demonstrateSmartPointers();
return 0;
}
- Use
unique_ptrfor exclusive ownership (default choice) - Use
shared_ptronly when shared ownership is truly needed - Use
weak_ptrto break reference cycles and for caching - Prefer
make_uniqueandmake_sharedover explicitnew - Never mix raw pointers and smart pointers for the same resource
- Use
enable_shared_from_thiswhen objects need to createshared_ptrto themselves - Consider custom deleters for non-memory resources
7. Modern C++ Best Practices
Modern C++ Best Practices
- Use auto for local variables when type is obvious
- Prefer range-based for loops over iterator-based loops
- Use smart pointers instead of raw pointers
- Use nullptr instead of NULL or 0
- Use constexpr for compile-time computations
- Use noexcept for functions that don't throw
- Use move semantics for efficient resource transfers
Common Modern C++ Mistakes
- Using auto when type isn't clear from context
- Capturing everything by reference in lambdas
- Using shared_ptr when unique_ptr would suffice
- Not marking move constructors as noexcept
- Forgetting to implement move operations
- Using old C headers instead of C++ equivalents
- Not using structured bindings when appropriate
#include <iostream>
#include <vector>
#include <memory>
#include <algorithm>
#include <string>
#include <map>
using namespace std;
// Traditional C++ (pre-C++11)
class TraditionalClass {
private:
int* data;
size_t size;
public:
// Manual memory management
TraditionalClass(size_t s) : size(s), data(new int[s]) {
// Initialize
}
// Rule of Three: copy constructor, copy assignment, destructor
TraditionalClass(const TraditionalClass& other)
: size(other.size), data(new int[other.size]) {
copy(other.data, other.data + size, data);
}
TraditionalClass& operator=(const TraditionalClass& other) {
if (this != &other) {
delete[] data;
size = other.size;
data = new int[size];
copy(other.data, other.data + size, data);
}
return *this;
}
~TraditionalClass() {
delete[] data;
}
// No move semantics
// Manual iteration
void print() const {
for (size_t i = 0; i < size; ++i) {
cout << data[i] << " ";
}
cout << endl;
}
};
// Modern C++ (C++11 and later)
class ModernClass {
private:
vector<int> data; // Use standard containers
public:
// Use initializer lists
ModernClass(initializer_list<int> init) : data(init) {}
// Rule of Zero: compiler generates everything
// No need to implement copy/move/destructor
// Use range-based for
void print() const {
for (const auto& value : data) {
cout << value << " ";
}
cout << endl;
}
// Use algorithms
int sum() const {
return accumulate(data.begin(), data.end(), 0);
}
};
void demonstrateComparison() {
cout << "=== MODERN VS TRADITIONAL C++ ===" << endl << endl;
// 1. Variable declaration
cout << "1. Variable Declaration:" << endl;
// Traditional
int x = 42;
vector<int>::iterator it;
// Modern
auto y = 42; // auto deduction
auto name = string("Modern"); // auto with constructor
auto numbers = vector{1, 2, 3}; // CTAD (C++17)
cout << "Traditional: explicit types" << endl;
cout << "Modern: auto where clear" << endl;
// 2. Loop iteration
cout << "\n2. Loop Iteration:" << endl;
vector<int> vec{1, 2, 3, 4, 5};
cout << "Traditional:" << endl;
for (vector<int>::iterator it = vec.begin(); it != vec.end(); ++it) {
cout << *it << " ";
}
cout << endl;
cout << "Modern:" << endl;
for (const auto& value : vec) {
cout << value << " ";
}
cout << endl;
// 3. Function return
cout << "\n3. Function Return:" << endl;
// Traditional
pair<int, string> makePairTraditional() {
return pair<int, string>(42, "answer");
}
// Modern
auto makePairModern() {
return make_pair(42, "answer"); // C++11
// or: return pair(42, "answer"); // C++17 CTAD
}
// Even better with structured binding
auto [num, str] = makePairModern();
cout << "Structured binding: " << num << ", " << str << endl;
// 4. Smart pointers vs raw pointers
cout << "\n4. Memory Management:" << endl;
// Traditional (dangerous)
int* rawPtr = new int(42);
// ... use rawPtr ...
delete rawPtr; // Must remember!
// Modern (safe)
auto smartPtr = make_unique<int>(42);
// No need to delete - automatically managed
// 5. String handling
cout << "\n5. String Handling:" << endl;
// Traditional
char oldStr[] = "Hello";
string cppStr = "World";
// Modern
string_view view = "Modern View"sv; // C++17
u8string utf8 = u8"UTF-8 String"; // C++20 char8_t
// 6. Map iteration
cout << "\n6. Map Iteration:" << endl;
map<int, string> m{{1, "one"}, {2, "two"}, {3, "three"}};
cout << "Traditional:" << endl;
for (map<int, string>::const_iterator it = m.begin(); it != m.end(); ++it) {
cout << it->first << ": " << it->second << endl;
}
cout << "Modern (C++11):" << endl;
for (const auto& pair : m) {
cout << pair.first << ": " << pair.second << endl;
}
cout << "Modern (C++17):" << endl;
for (const auto& [key, value] : m) {
cout << key << ": " << value << endl;
}
// 7. Algorithm usage
cout << "\n7. Algorithm Usage:" << endl;
vector<int> data{1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
// Traditional
vector<int> evenTraditional;
for (size_t i = 0; i < data.size(); ++i) {
if (data[i] % 2 == 0) {
evenTraditional.push_back(data[i]);
}
}
// Modern (C++11)
vector<int> evenModern;
copy_if(data.begin(), data.end(), back_inserter(evenModern),
[](int n) { return n % 2 == 0; });
// Modern (C++20)
auto evenRanges = data | views::filter([](int n) { return n % 2 == 0; });
cout << "Traditional: manual loop" << endl;
cout << "Modern (C++11): copy_if with lambda" << endl;
cout << "Modern (C++20): ranges pipeline" << endl;
// 8. Error handling
cout << "\n8. Error Handling:" << endl;
// Traditional
int* allocateTraditional(size_t size) {
if (size == 0) return nullptr; // Sentinel value
return new int[size];
}
// Modern (C++11)
unique_ptr<int[]> allocateModern(size_t size) {
if (size == 0) return nullptr; // Still nullptr, but wrapped
return make_unique<int[]>(size);
}
// Modern (C++17)
/*
expected<unique_ptr<int[]>, string> allocateExpected(size_t size) {
if (size == 0) {
return unexpected{"Size cannot be zero"};
}
return make_unique<int[]>(size);
}
*/
cout << "Traditional: nullptr sentinel" << endl;
cout << "Modern (C++11): smart pointer with nullptr" << endl;
cout << "Modern (C++17): std::expected for rich error information" << endl;
// 9. Class design
cout << "\n9. Class Design:" << endl;
// Traditional: Rule of Three
class RuleOfThree {
int* data;
public:
RuleOfThree(int size) : data(new int[size]) {}
~RuleOfThree() { delete[] data; }
RuleOfThree(const RuleOfThree& other); // Copy constructor
RuleOfThree& operator=(const RuleOfThree& other); // Copy assignment
// No move operations
};
// Modern: Rule of Zero or Rule of Five
class RuleOfZero {
vector<int> data; // Resource managed by member
public:
RuleOfZero(initializer_list<int> init) : data(init) {}
// Compiler generates everything correctly
};
class RuleOfFive {
unique_ptr<int[]> data;
size_t size;
public:
RuleOfFive(size_t s) : size(s), data(make_unique<int[]>(s)) {}
~RuleOfFive() = default;
// Copy operations (if needed)
RuleOfFive(const RuleOfFive& other)
: size(other.size), data(make_unique<int[]>(other.size)) {
copy(other.data.get(), other.data.get() + size, data.get());
}
RuleOfFive& operator=(const RuleOfFive& other) {
if (this != &other) {
auto temp = make_unique<int[]>(other.size);
copy(other.data.get(), other.data.get() + other.size, temp.get());
data = move(temp);
size = other.size;
}
return *this;
}
// Move operations (automatically noexcept)
RuleOfFive(RuleOfFive&& other) noexcept = default;
RuleOfFive& operator=(RuleOfFive&& other) noexcept = default;
};
cout << "Traditional: Rule of Three (copy operations + destructor)" << endl;
cout << "Modern: Rule of Zero (let compiler handle everything)" << endl;
cout << "Modern: Rule of Five (add move operations)" << endl;
cout << "\n=== KEY TAKEAWAYS ===" << endl;
cout << "1. Modern C++ is safer (smart pointers, range-based for)" << endl;
cout << "2. Modern C++ is more expressive (auto, lambdas, structured bindings)" << endl;
cout << "3. Modern C++ is more efficient (move semantics, constexpr)" << endl;
cout << "4. Modern C++ reduces boilerplate (auto, range-based for, algorithms)" << endl;
cout << "5. Modern C++ enables new paradigms (ranges, coroutines, concepts)" << endl;
}
int main() {
demonstrateComparison();
return 0;
}