Modern C++ Features: constexpr, Structured Bindings, optional, variant & std::format Complete Masterclass
Welcome to Phase 20 (Chapter 20): Modern C++ Features Masterclass! C++11 through C++23 brought a revolution in expressiveness, safety, and performance. This chapter covers the most impactful modern features: auto, constexpr, structured bindings, std::optional, std::variant, std::format, Concepts, coroutines introduction, and Modules overview.
#include <iostream>
#include <vector>
#include <map>
#include <string>
#include <memory>
int main() {
// auto โ compile-time type deduction, zero overhead
auto i = 42; // int
auto d = 3.14; // double
auto s = std::string{"hello"}; // std::string
auto v = std::vector<int>{1, 2, 3, 4, 5};
auto p = std::make_unique<int>(99);
std::cout << "auto types: " << i << " " << d << " " << s << "
";
// auto with references
const auto& ref = v; // const vector<int>&
auto& mref = v; // vector<int>&
// nullptr โ type-safe null (replaces NULL and 0)
int* raw = nullptr;
void* vp = nullptr;
if (raw == nullptr) std::cout << "raw is null
";
// raw == 0; // works but unclear
// raw == NULL; // might cause overload ambiguity
// Uniform brace initialization โ prevents narrowing!
int a{5};
double db{3.14};
// int bad{3.14}; // COMPILE ERROR โ narrowing from double to int!
std::vector<int> vec{10, 20, 30, 40};
std::map<std::string, int> m{{"one",1}, {"two",2}, {"three",3}};
// Range-based for (C++11)
std::cout << "vec: ";
for (auto x : vec) std::cout << x << " ";
std::cout << "
";
// Range-based for with init (C++20)
for (auto copy = vec; auto x : copy) std::cout << x * 2 << " ";
std::cout << "
";
// enum class โ scoped, strongly typed, no implicit int conversion
enum class Color { Red, Green, Blue };
enum class Direction { North, South, East, West };
Color c = Color::Red;
Direction d2 = Direction::North;
// if (c == d2) {} // COMPILE ERROR โ different types!
// if (c == 0) {} // COMPILE ERROR โ no implicit int conversion!
if (c == Color::Red) std::cout << "Red!
";
// Specify underlying type for enum class
enum class Status : uint8_t { OK = 0, Error = 1, Pending = 2 };
Status s2 = Status::OK;
std::cout << "status raw: " << (int)s2 << "
"; // explicit cast OK
return 0;
}
#include <iostream>
#include <array>
#include <cmath>
// constexpr function โ evaluated at compile time if inputs are compile-time
constexpr int factorial(int n) {
return n <= 1 ? 1 : n * factorial(n - 1);
}
constexpr double circleArea(double r) {
return 3.14159265358979 * r * r;
}
constexpr bool isPrime(int n) {
if (n < 2) return false;
for (int i = 2; i * i <= n; ++i)
if (n % i == 0) return false;
return true;
}
// consteval (C++20) โ MUST be compile-time only
consteval int pow2(int n) {
return 1 << n;
}
// constinit (C++20) โ constant initialization of static/thread_local vars
constinit int globalVal = factorial(5); // guaranteed compile-time init
// Compile-time array using constexpr
template <int N>
constexpr auto makePrimes() {
std::array<int, N> primes{};
int count = 0;
for (int i = 2; count < N; ++i) {
if (isPrime(i)) primes[count++] = i;
}
return primes;
}
int main() {
// compile-time constants
constexpr int fact10 = factorial(10); // 3628800 โ computed at compile time!
constexpr double area5 = circleArea(5.0);
std::cout << "10! = " << fact10 << "
";
std::cout << "Area(r=5) = " << area5 << "
";
// runtime usage (also valid for constexpr functions)
int n;
std::cin >> n;
std::cout << "Runtime factorial(" << n << ") = " << factorial(n) << "
";
// consteval โ compile-time only
constexpr int p8 = pow2(8); // 256 at compile time
std::cout << "2^8 = " << p8 << "
";
// int x; pow2(x); // COMPILE ERROR โ runtime value not allowed!
// Compile-time prime table
constexpr auto first10Primes = makePrimes<10>();
std::cout << "First 10 primes: ";
for (int p : first10Primes) std::cout << p << " ";
std::cout << "
";
// constinit
std::cout << "globalVal (5!) = " << globalVal << "
";
return 0;
}
#include <iostream>
#include <map>
#include <tuple>
#include <string>
#include <vector>
#include <optional>
// Return multiple values cleanly
struct ParseResult {
int value;
bool success;
std::string error;
};
ParseResult parseNumber(const std::string& s) {
try {
return {std::stoi(s), true, ""};
} catch (const std::exception& e) {
return {0, false, e.what()};
}
}
// decltype โ type of expression at compile time
template <typename T, typename U>
auto safeAdd(T a, U b) -> decltype(a + b) {
return a + b;
}
int main() {
// โโโ Structured Bindings (C++17) โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
// pair
auto pair = std::make_pair(42, std::string("hello"));
auto [num, str] = pair;
std::cout << "pair: " << num << " " << str << "
";
// tuple
auto t = std::make_tuple(1, 3.14, std::string("C++20"), true);
auto [id, pi, lang, flag] = t;
std::cout << id << " " << pi << " " << lang << " " << flag << "
";
// struct decomposition
auto [val, ok, err] = parseNumber("42");
if (ok) std::cout << "Parsed: " << val << "
";
auto [val2, ok2, err2] = parseNumber("bad");
if (!ok2) std::cout << "Parse error: " << err2 << "
";
// map iteration with structured bindings
std::map<std::string, int> scores{{"Alice",95}, {"Bob",87}, {"Charlie",92}};
for (const auto& [name, score] : scores) {
std::cout << name << ": " << score << "
";
}
// Modify via reference binding
for (auto& [name, score] : scores) score += 5; // give everyone +5
// โโโ if with initializer (C++17) โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
if (auto it = scores.find("Alice"); it != scores.end()) {
std::cout << "Alice's score: " << it->second << "
";
// it is scoped to this if block only!
}
// โโโ switch with initializer (C++17) โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
switch (auto [v, s, e] = parseNumber("100"); s ? v : -1) {
case -1: std::cout << "Parse failed
"; break;
case 100: std::cout << "Got 100!
"; break;
default: std::cout << "Got: " << v << "
"; break;
}
// โโโ decltype โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
int x = 5; double y = 3.14;
decltype(x + y) result = x + y; // result is double
decltype(x) copy = x; // copy is int
std::cout << "decltype result: " << result << "
";
std::cout << "safeAdd(3, 4.5) = " << safeAdd(3, 4.5) << "
";
return 0;
}
#include <iostream>
#include <format> // C++20
#include <chrono>
#include <string>
#include <vector>
#include <cmath>
// Benchmark helper using chrono
template <typename Func>
double measureMs(Func&& fn, int iterations = 1) {
auto start = std::chrono::high_resolution_clock::now();
for (int i = 0; i < iterations; ++i) fn();
auto end = std::chrono::high_resolution_clock::now();
auto dur = std::chrono::duration<double, std::milli>(end - start);
return dur.count();
}
int main() {
// โโโ std::format (C++20) โ type-safe, expressive formatting โโโโโโโโโโ
std::string s1 = std::format("Hello, {}!", "World");
std::string s2 = std::format("Pi = {:.4f}", 3.14159265);
std::string s3 = std::format("{:>10} | {:<10} | {:^10}", "right", "left", "center");
std::string s4 = std::format("Hex: {:x} Oct: {:o} Bin: {:b}", 255, 255, 255);
std::string s5 = std::format("Sci: {:e}", 1234567.89);
std::string s6 = std::format("{0} {1} {0}", "echo", "this"); // positional
std::cout << s1 << "
" << s2 << "
" << s3 << "
"
<< s4 << "
" << s5 << "
" << s6 << "
";
// Table formatting
std::cout << std::format("
{:-<30}
", ""); // separator line
std::cout << std::format("{:<15} {:>8} {:>6}
", "Name", "Score", "Grade");
std::cout << std::format("{:-<30}
", "");
for (auto [name, score] : std::vector<std::pair<std::string, int>>{
{"Alice",95}, {"Bob",87}, {"Charlie",72}}) {
char grade = score>=90?'A': score>=80?'B': 'C';
std::cout << std::format("{:<15} {:>8} {:>6}
", name, score, grade);
}
// โโโ std::chrono โ time points, durations, clocks โโโโโโโโโโโโโโโโโโโโ
// High-resolution benchmark
auto ms = measureMs([]() {
double sum = 0;
for (int i = 0; i < 1'000'000; ++i) sum += std::sqrt(i);
return sum;
});
std::cout << std::format("
Benchmark: {:.3f} ms
", ms);
// Duration arithmetic
using namespace std::chrono_literals;
auto d1 = 2h + 30min + 45s;
std::cout << "Duration: " << std::chrono::duration_cast<std::chrono::seconds>(d1).count() << " seconds
";
// Current time
auto now = std::chrono::system_clock::now();
auto time_t_now = std::chrono::system_clock::to_time_t(now);
std::cout << "Now: " << std::ctime(&time_t_now);
return 0;
}
#include <iostream>
#include <string>
#include <vector>
#include <utility>
class BigBuffer {
std::vector<int> data_;
std::string name_;
public:
BigBuffer(std::string name, std::size_t size)
: data_(size, 0), name_{std::move(name)} {
std::cout << "Constructed " << name_ << " (" << size << " ints)
";
}
// Copy constructor โ expensive
BigBuffer(const BigBuffer& other)
: data_{other.data_}, name_{other.name_ + "_copy"} {
std::cout << "COPY: " << name_ << " (copying " << data_.size() << " ints)
";
}
// Move constructor โ cheap (steals resources)
BigBuffer(BigBuffer&& other) noexcept
: data_{std::move(other.data_)}, name_{std::move(other.name_) + "_moved"} {
std::cout << "MOVE: " << name_ << " (zero copy!)
";
}
std::size_t size() const { return data_.size(); }
const std::string& name() const { return name_; }
};
// Perfect forwarding โ forward args to constructor without extra copies
template <typename T, typename... Args>
T createObject(Args&&... args) {
return T(std::forward<Args>(args)...);
}
BigBuffer makeBuffer(std::string name) {
BigBuffer local{std::move(name), 1000};
return local; // NRVO (Named Return Value Optimization) โ likely no copy/move!
}
int main() {
std::cout << "=== Copy vs Move ===
";
BigBuffer b1{"original", 1000};
BigBuffer b2{b1}; // COPY (expensive)
BigBuffer b3{std::move(b1)}; // MOVE (cheap!) โ b1 is now empty
std::cout << "b1 size after move: " << b1.size() << "
"; // 0
std::cout << "b3 size: " << b3.size() << "
"; // 1000
std::cout << "
=== Return Value Optimization ===
";
BigBuffer b4 = makeBuffer("factory"); // NRVO โ no move needed
std::cout << "
=== Perfect Forwarding ===
";
auto b5 = createObject<BigBuffer>(std::string("forwarded"), 500);
std::cout << "
=== std::move with string ===
";
std::string s1 = "Hello World (large string with much content)";
std::string s2 = std::move(s1); // move: s2 gets content, s1 becomes empty
std::cout << "s1 empty: " << s1.empty() << "
";
std::cout << "s2: " << s2 << "
";
return 0;
}
Q1: What is the difference between constexpr and consteval?
constexpr functions may run at compile-time OR runtime. consteval (C++20) mandates compile-time evaluation โ calling it with a runtime value is a compile error. Use consteval for pure compile-time computations (lookup tables, templates).
Q2: Why is std::format preferred over printf?
std::format is type-safe (checked at compile time), returns std::string, is extensible for custom types (via std::formatter), and doesn't use va_args. printf has no type-checking โ printf("%d", 3.14) is UB.
Q3: What is an rvalue reference?
T&& is an rvalue reference โ it binds to temporary objects. Used in move constructors and move assignments to "steal" resources from temporaries instead of copying. std::move(x) casts x to an rvalue reference, enabling the move.
Q4: What is perfect forwarding?
std::forward<T>(arg) in a template function forwards arguments with their original value category (lvalue stays lvalue, rvalue stays rvalue). Used in wrapper functions and factory templates to avoid unnecessary copies.
Q5: What are C++20 Modules?
Modules replace header files: export module mylib; declares the module, import mylib; uses it. Advantages: no multiple-inclusion issues, no macro leakage, significantly faster compilation (no re-parsing headers), and better encapsulation.