Chapter 1 C++11
C++98, as the first standard version of C++, laid the basic framework of the language, but gradually exposed issues such as cumbersome type inference, high memory management risks, and insufficient generic programming capabilities when facing modern software development needs. C++11, as a milestone update, introduced a large number of new features, achieving a qualitative leap in syntax simplicity, memory safety, and development efficiency. The following shows the core differences through source code comparisons:
Automatic Type Inference (auto Keyword)
In C++98, variable declarations must explicitly specify the type, which can lead to redundant and error-prone code when type names are lengthy (e.g., iterators of complex containers); C++11 introduced the auto keyword, which can automatically infer the variable type based on the initialization expression, simplifying code writing.
// C++98 Implementation: Explicitly specify iterator type, verbose and error-prone
#include <vector>
int main() {
std::vector<int> vec;
// Must fully write out the iterator type std::vector<int>::iterator
for (std::vector<int>::iterator it = vec.begin(); it != vec.end(); ++it) {
*it = 1;
}
return 0;
}
// C++11 Implementation: auto automatically infers iterator type, concise and intuitive
#include <vector>
int main() {
std::vector<int> vec;
// auto automatically infers to std::vector<int>::iterator, reducing redundancy
for (auto it = vec.begin(); it != vec.end(); ++it) {
*it = 1;
}
return 0;
}
Smart Pointers (shared_ptr/unique_ptr)
C++98 only provided auto_ptr smart pointer, which has copy semantics flaws (the original pointer becomes invalid after copying) and cannot solve circular reference issues, leading to high memory leak risks in practical use; C++11 introduced shared_ptr (shared ownership) and unique_ptr (exclusive ownership), improving the smart pointer system and significantly enhancing memory management safety.
// C++98 Implementation: Using auto_ptr, with copy semantics flaws
#include <memory>
#include <iostream>
int main() {
std::auto_ptr<int> ptr1(new int(10));
// After copying, ptr1 becomes invalid, using ptr1 again will trigger undefined behavior
std::auto_ptr<int> ptr2 = ptr1;
std::cout << *ptr2 << std::endl; // Outputs 10
// std::cout << *ptr1 << std::endl; // Undefined behavior, program may crash
return 0;
}
// C++11 Implementation: Using shared_ptr/unique_ptr, clear and safe semantics
#include <memory>
#include <iostream>
int main() {
// shared_ptr: shared ownership, reference counting manages lifecycle
std::shared_ptr<int> ptr1(new int(10));
std::shared_ptr<int> ptr2 = ptr1;
std::cout << *ptr1 << " " << *ptr2 << std::endl; // Outputs 10 10
std::cout << ptr1.use_count() << std::endl; // Outputs 2, clear reference count
// unique_ptr: exclusive ownership, disallow copying, only supports moving
std::unique_ptr<int> ptr3(new int(20));
// std::unique_ptr<int> ptr4 = ptr3; // Compilation error, disallow copying
std::unique_ptr<int> ptr4 = std::move(ptr3); // After moving, ptr3 becomes invalid, clear semantics
std::cout << *ptr4 << std::endl; // Outputs 20
return 0;
}
Range-based for Loop
In C++98, traversing containers requires using iterators or indices, leading to templated code that is prone to boundary errors; C++11 introduced range-based for loops, allowing direct traversal of all elements in a container or array, with concise syntax and reduced error risk.
// C++98 Implementation: Traversing container using iterators, verbose code
#include <vector>
#include <iostream>
int main() {
std::vector<int> vec = {1, 2, 3, 4}; // C++98 does not support list initialization, must manually push_back
vec.push_back(1);
vec.push_back(2);
vec.push_back(3);
vec.push_back(4);
for (std::vector<int>::iterator it = vec.begin(); it != vec.end(); ++it) {
std::cout << *it << " "; // Outputs 1 2 3 4
}
return 0;
}
// C++11 Implementation: Range-based for loop + list initialization, concise and efficient
#include <vector>
#include <iostream>
int main() {
// List initialization: new feature in C++11, directly initialize container
std::vector<int> vec = {1, 2, 3, 4};
// Range-based for loop: directly traverse all elements, no need to care about iterators
for (int val : vec) {
std::cout << val << " "; // Outputs 1 2 3 4
}
// If modification of elements is needed, references can be used
for (int& val : vec) {
val *= 2;
}
for (int val : vec) {
std::cout << val << " "; // Outputs 2 4 6 8
}
return 0;
}
Chapter 2 C++17
C++11 solved many fundamental issues of C++98, while C++17 further optimized development efficiency based on C++11, focusing on code simplification, performance improvement, and standard library enhancement, addressing C++11’s shortcomings in structured data processing, compile-time evaluation, and file system support. The following are core feature comparisons:
Structured Bindings
In C++11, accessing multiple members of a struct or tuple required retrieving them one by one, resulting in verbose code; C++17 introduced structured bindings, allowing multiple members of a struct or tuple to be bound to multiple variables at once, simplifying data extraction operations.
// C++11 Implementation: Retrieve struct/tuple members one by one, verbose code
#include <tuple>
#include <iostream>
struct Person {
std::string name;
int age;
};
int main() {
Person p = {"Alice", 25};
// Retrieve members one by one, verbose code if there are many members
std::string name = p.name;
int age = p.age;
std::cout << name << " " << age << std::endl; // Outputs Alice 25
std::tuple<std::string, int, double> t = {"Bob", 30, 180.5};
// Retrieve tuple members using get, must specify index, prone to errors
std::string t_name = std::get<0>(t);
int t_age = std::get<1>(t);
double t_height = std::get<2>(t);
std::cout << t_name << " " << t_age << " " << t_height << std::endl; // Outputs Bob 30 180.5
return 0;
}
// C++17 Implementation: Structured bindings, extract multiple members at once
#include <tuple>
#include <iostream>
struct Person {
std::string name;
int age;
};
int main() {
Person p = {"Alice", 25};
// Structured bindings: bind struct's name and age members at once
auto [name, age] = p;
std::cout << name << " " << age << std::endl; // Outputs Alice 25
std::tuple<std::string, int, double> t = {"Bob", 30, 180.5};
// Structured bindings: bind three members of tuple at once, no need to specify index
auto [t_name, t_age, t_height] = t;
std::cout << t_name << " " << t_age << " " << t_height << std::endl; // Outputs Bob 30 180.5
// Supports reference binding, modifying bound variables modifies original data
auto& [ref_name, ref_age] = p;
ref_name = "Alice_New";
std::cout << p.name << std::endl; // Outputs Alice_New
return 0;
}
if constexpr Compile-time Conditional Evaluation
In C++11, conditional evaluations were executed at runtime, and for scenarios where different logic needed to be executed in templates due to different types, it could only be achieved through template specialization, leading to verbose code; C++17 introduced if constexpr, allowing conditions to be evaluated at compile time and generating corresponding code, reducing runtime overhead and simplifying template logic.
// C++11 Implementation: Need to implement different logic through template specialization, verbose code
#include <iostream>
#include <string>// General template
template <typename T>void printType(T val) {
// Cannot determine T's type at compile time, can only implement different logic through specialization
std::cout << "Unknown type" << std::endl;
}// Specialization for int type
template <>void printType(int val) {
std::cout << "Int type: " << val << std::endl;
}// Specialization for std::string type
template <>void printType(std::string val) {
std::cout << "String type: " << val << std::endl;
}
int main() {
printType(10); // Outputs Int type: 10
printType(std::string("test")); // Outputs String type: test
printType(3.14); // Outputs Unknown type
return 0;
}
// C++17 Implementation: if constexpr compile-time evaluation, no need for template specialization
#include <iostream>
#include <string>
#include <type_traits>// Single template can handle different types, logic centralized
template <typename T>void printType(T val) {
// if constexpr: compile-time evaluation of T's type, only generate code that meets conditions
if constexpr (std::is_same_v<T, int>) {
std::cout << "Int type: " << val << std::endl;
} else if constexpr (std::is_same_v<T, std::string>) {
std::cout << "String type: " << val << std::endl;
} else if constexpr (std::is_floating_point_v<T>) {
std::cout << "Floating point type: " << val << std::endl;
} else {
std::cout << "Unknown type" << std::endl;
}}
int main() {
printType(10); // Outputs Int type: 10
printType(std::string("test")); // Outputs String type: test
printType(3.14); // Outputs Floating point type: 3.14
return 0;
}
Standard Library File System (std::filesystem)
Standards prior to C++11 did not provide native file system operation interfaces, relying on operating system APIs (such as Windows’ Win32 API, Linux’s POSIX API), leading to poor code portability; C++17 incorporated the file system into the standard library (std::filesystem), providing a unified file operation interface and enhancing code portability.
// C++11 Implementation: Relies on Linux POSIX API, poor portability
#include <iostream>
#include <cstring>
#include <dirent.h> // Linux-specific header file, not supported on Windows
void listDir(const char* path) {
DIR* dir = opendir(path);
if (!dir) {
std::cerr << "Open dir failed: " << strerror(errno) << std::endl;
return;
}
struct dirent* entry;
while ((entry = readdir(dir)) != nullptr) {
std::cout << entry->d_name << std::endl;
}
closedir(dir);}
int main() {
listDir("."); // List files in the current directory, only effective in Linux
return 0;
}
// C++17 Implementation: Using std::filesystem, cross-platform unified interface
#include <iostream>
#include <filesystem> // Standard library header file, cross-platform support
namespace fs = std::filesystem;
void listDir(const std::string& path) {
try {
// Unified interface for traversing directories, effective on Windows/Linux/Mac
for (const auto& entry : fs::directory_iterator(path)) {
// Get file name and output
std::cout << entry.path().filename() << std::endl;
}
} catch (const fs::filesystem_error& e) {
std::cerr << "File system error: " << e.what() << std::endl;
}}
int main() {
listDir("."); // List files in the current directory, effective cross-platform
// Additional features: check if path is a directory, get file size, etc.
fs::path p(".");
if (fs::is_directory(p)) {
std::cout << "Is directory" << std::endl;
}
return 0;
}
Chapter 3 C++20
C++20 is a major update to the C++ language, focusing on the ultimate expression of “modern C++” by introducing revolutionary features such as Concepts, Coroutines, and Ranges, addressing C++17’s shortcomings in template type constraints, asynchronous programming, and data processing pipelines, significantly enhancing code readability, maintainability, and performance.
Concepts: Template Type Constraints
Templates prior to C++17 lacked explicit type constraints, leading to obscure error messages from the compiler when incompatible types were passed; C++20 introduced Concepts, allowing explicit definition of constraints on template parameters, making template interfaces clearer and error messages more intuitive.
// C++17 Implementation: Templates without explicit constraints, obscure error messages
#include <iostream>
#include <string>// Template expects arithmetic types, but lacks explicit constraints
template <typename T>T add(T a, T b) {
return a + b;}
int main() {
std::cout << add(1, 2) << std::endl; // Correct, outputs 3
// Passing std::string type, compilation error, but error message is lengthy and obscure
// std::cout << add(std::string("a"), std::string("b")) << std::endl;
/* Example of error message (simplified):
error: no match for 'operator+' (operand types are 'std::string' and 'std::string')
Cannot directly see that the template parameter type does not meet the requirements
*/
return 0;
}
// C++20 Implementation: Use Concepts to explicitly constrain template parameters
#include <iostream>
#include <string>
#include <concepts> // Header file related to Concepts
// Define concept Arithmetic: constrain T to be an arithmetic type (int, double, etc.)
template <typename T>concept Arithmetic = std::is_arithmetic_v<T>;
// Template parameter T must satisfy the Arithmetic concept
template <Arithmetic T>T add(T a, T b) {
return a + b;
}// Can also use requires expressions to define constraints within templates
template <typename T>requires std::is_integral_v<T> // Constrain T to be an integral type
T multiply(T a, T b) {
return a * b;
}
int main() {
std::cout << add(1, 2) << std::endl; // Correct, outputs 3
std::cout << add(3.14, 2.5) << std::endl; // Correct, outputs 5.64
std::cout << multiply(2, 3) << std::endl; // Correct, outputs 6
// Passing std::string type, compilation error, clear error message
// std::cout << add(std::string("a"), std::string("b")) << std::endl;
/* Example of error message (simplified):
error: template constraint failure for 'template <Arithmetic T> T add(T, T)'
note: constraints not satisfied: 'std::is_arithmetic_v<std::string>' is false
Directly points out that the Arithmetic concept is not satisfied, reason is std::string is not an arithmetic type
*/
return 0;
}
Coroutines: Lightweight Asynchronous Programming
Prior to C++17, asynchronous programming required using callback functions, futures/promises, etc., leading to scattered code logic (callback hell) and high memory overhead; C++20 introduced coroutines, allowing asynchronous logic to be written in a synchronous code style by pausing and resuming function execution, simplifying code structure and reducing memory overhead.
// C++17 Implementation: Using std::future for asynchronous, logic is more complicated
#include <iostream>
#include <future>
#include <chrono>// Simulate time-consuming operations (e.g., network requests, file reading)
int fetchData() {
std::this_thread::sleep_for(std::chrono::seconds(2)); // Simulate 2 seconds of delay
return 42;}
int main() {
// Start asynchronous task, returns std::future
std::future<int> fut = std::async(std::launch::async, fetchData);
// Main thread can perform other operations
std::cout << "Main thread doing other work..." << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(1));
// Wait for asynchronous task to complete and get result (blocking)
int result = fut.get();
std::cout << "Fetched data: " << result << std::endl; // Outputs Fetched data: 42
// If multiple asynchronous tasks need to be chained, need to nest futures, prone to logical confusion
return 0;
}
// C++20 Implementation: Using coroutines for asynchronous, write asynchronous logic in synchronous style
#include <iostream>
#include <coroutine>
#include <chrono>
#include <thread>// Define coroutine return type (simplified, in actual projects can use libraries like cppcoro)
struct Task {
struct promise_type {
int result;
Task get_return_object() { return Task(this); }
std::suspend_never initial_suspend() { return {}; }
std::suspend_never final_suspend() noexcept { return {}; }
void return_value(int val) { result = val; }
void unhandled_exception() { std::terminate(); }
};
promise_type* prom;
Task(promise_type* p) : prom(p) {}
int get_result() { return prom->result; }};// Simulate time-consuming asynchronous operation, returns coroutine Task
Task fetchDataAsync() {
// Simulate time-consuming operation, use suspend to simulate waiting for completion
std::cout << "Fetching data start..." << std::endl;
co_await std::suspend_always{}; // Pause coroutine, simulate waiting for time-consuming operation to complete
std::cout << "Fetching data done..." << std::endl;
co_return 42; // Return result after coroutine resumes
}
int main() {
Task task = fetchDataAsync(); // Start coroutine, execute until co_await pauses
// Main thread performs other operations
std::cout << "Main thread doing other work..." << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(2)); // Simulate main thread work
// Resume coroutine execution (in actual projects triggered by asynchronous events, such as IO completion)
std::coroutine_handle<Task::promise_type> handle =
std::coroutine_handle<Task::promise_type>::from_promise(*task.prom);
handle.resume();
// Get coroutine result
std::cout << "Fetched data: " << task.get_result() << std::endl; // Outputs Fetched data: 42
// When chaining multiple asynchronous tasks, can write in sequence, logic is clear
// Task task1 = fetchDataAsync();
// co_await task1;
// Task task2 = processDataAsync(task1.get_result());
// co_await task2;
return 0;
}
Ranges Library: More Elegant Data Processing
Prior to C++17, processing container data required using the algorithm library (<algorithm>), separating algorithms from containers, requiring manual passing of iterator start and end, and not supporting chained calls, leading to poor code readability; C++20 introduced the ranges library, merging containers and algorithms, supporting chained calls and lazy evaluation, simplifying the writing of data processing pipelines.
// C++17 Implementation: Using algorithm library to process data, need to pass iterators, no chained calls
#include <iostream>
#include <vector>
#include <algorithm>
#include <numeric>
int main() {
std::vector<int> vec = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
std::vector<int> temp, result;
// Requirement: filter even numbers → multiply by 2 → sum
// Step 1: filter even numbers, need to manually pass iterators, and need intermediate container temp
std::copy_if(vec.begin(), vec.end(), std::back_inserter(temp),
[](int val) { return val % 2 == 0; });
// Step 2: multiply even numbers by 2, need another intermediate container result
std::transform(temp.begin(), temp.end(), std::back_inserter(result),
[](int val) { return val * 2; });
// Step 3: sum
int sum = std::accumulate(result.begin(), result.end(), 0);
std::cout << "Sum: " << sum << std::endl; // Outputs Sum: 60 (2*2 +4*2 +...+10*2=60)
// Problem: need multiple intermediate containers, verbose code, does not support chained calls
return 0;
}
// C++20 Implementation: Using Ranges library, chained calls + lazy evaluation
#include <iostream>
#include <vector>
#include <ranges> // Ranges library header file
#include <numeric>
int main() {
std::vector<int> vec = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
// Requirement: filter even numbers → multiply by 2 → sum
// Chained calls: directly connect multiple adapters with |, no need for intermediate containers
auto filtered_transformed = vec | std::views::filter([](int val) { return val % 2 == 0; })
| std::views::transform([](int val) { return val * 2; });
// Sum: ranges adapted can be directly passed to algorithms
int sum = std::accumulate(filtered_transformed.begin(), filtered_transformed.end(), 0);
std::cout << "Sum: " << sum << std::endl; // Outputs Sum: 60
// Lazy evaluation: filtering and transforming are executed during traversal (e.g., accumulate), improving performance
// Additional advantage: supports direct traversal, no need for intermediate containers
for (int val : filtered_transformed) {
std::cout << val << " "; // Outputs 4 8 12 16 20
}
return 0;
}
Chapter 4 C++23
C++23, as an iterative version of C++20, focuses on “perfection and optimization”, supplementing core features already introduced in C++20 (such as coroutines, ranges library) while addressing some pain points in daily development, such as error handling, string operations, and template syntax simplification, further enhancing development efficiency and language expressiveness.
std::expected: Return Value with Error Information
Prior to C++20, handling function errors typically used “return error code + output parameter” or “throw exceptions”, where error codes required manual mapping to error messages, and exceptions had performance overhead; C++23 introduced std::expected, allowing simultaneous return of “correct result” or “error information”, combining the lightweight nature of error codes with the richness of information in exceptions.
// C++20 Implementation: Insufficient error handling methods
#include <iostream>
#include <string>
#include <stdexcept>// Method 1: Return error code, need to manually maintain mapping of error code and information
enum class ErrorCode {
OK,
FILE_NOT_FOUND,
PERMISSION_DENIED};
ErrorCode readFile(const std::string& path, std::string& content) {
if (path.empty()) return ErrorCode::FILE_NOT_FOUND;
// Simulate read failure
content = "";
return ErrorCode::PERMISSION_DENIED;
}// Method 2: Throw exceptions, performance overhead is large
std::string readFileWithException(const std::string& path) {
if (path.empty()) throw std::runtime_error("File not found");
throw std::runtime_error("Permission denied");
}
int main() {
// Method 1 usage: need to check error code and manually convert to message
std::string content;
ErrorCode ec = readFile("test.txt", content);
if (ec != ErrorCode::OK) {
std::string errMsg;
if (ec == ErrorCode::FILE_NOT_FOUND) errMsg = "File not found";
else if (ec == ErrorCode::PERMISSION_DENIED) errMsg = "Permission denied";
std::cerr << "Error: " << errMsg << std::endl;
}
// Method 2 usage: need to catch exceptions, performance overhead is large
try {
readFileWithException("test.txt");
} catch (const std::exception& e) {
std::cerr << "Error: " << e.what() << std::endl;
}
return 0;
}
// C++23 Implementation: Use std::expected, balancing lightweight and rich information
#include <iostream>
#include <string>
#include <expected> // Header file for std::expected
#include <system_error>// Function returns std::expected<correct type, error type>
std::expected<std::string, std::error_code> readFile(const std::string& path) {
if (path.empty()) {
// Return error: use std::error_code, carries error code and information
return std::unexpected(std::make_error_code(std::errc::no_such_file_or_directory));
}
if (path == "forbidden.txt") {
return std::unexpected(std::make_error_code(std::errc::permission_denied));
}
// Return correct result
return "File content: Hello C++23";}
int main() {
// Method 1: Use has_value() to check success
auto result1 = readFile("test.txt");
if (result1.has_value()) {
std::cout << "Success: " << result1.value() << std::endl;
} else {
// Directly get error information, no need for manual mapping
std::cerr << "Error: " << result1.error().message() << std::endl;
}
// Method 2: Use operator* and operator bool, more concise
auto result2 = readFile("forbidden.txt");
if (result2) {
std::cout << "Success: " << *result2 << std::endl;
} else {
std::cerr << "Error: " << result2.error().message() << std::endl; // Outputs Permission denied
}
// Method 3: Use value_or to get default value
std::string content = readFile("").value_or("Default content");
std::cout << "Content: " << content << std::endl; // Outputs Default content
return 0;
}
String Enhancements: std::string_view and String Literals
While std::string_view in C++20 solved the string copy issue, it still had shortcomings in interacting with C-style strings and concatenation; C++23 enhanced the functionality of std::string_view and introduced UTF-8 string literals, addressing the pain points of multi-byte string processing.
// C++20 Implementation: Shortcomings of std::string_view
#include <iostream>
#include <string_view>
#include <string>
int main() {
std::string_view sv = "Hello C++20";
// 1. Cannot directly convert to C-style string (need to manually check if it ends with '\0')
const char* cstr = sv.data();
// If sv is obtained from a non-'\0' terminated buffer, using cstr directly will cause errors
std::cout << cstr << std::endl; // This is lucky to be correct, but there are hidden dangers
// 2. Cannot directly concatenate strings
std::string_view sv2 = " World";
// std::string_view sv3 = sv + sv2; // Compilation error, string_view has no operator+
// 3. No native UTF-8 string literal support, need to handle manually
const char* utf8_str = u8"你好"; // C++20 only supports u8 prefix, but no type distinction
// Cannot directly determine if the string is of UTF-8 type
return 0;
}
// C++23 Implementation: String enhancement features
#include <iostream>
#include <string_view>
#include <string>
int main() {
// 1. std::string_view adds data() overload, ensuring return of '\0' terminated C-style string
std::string_view sv = "Hello C++23";
const char* cstr = sv.data(); // C++23 guarantees that the returned cstr is '\0' terminated, safe to use
std::cout << cstr << std::endl; // Outputs Hello C++23
// 2. std::string_view supports operator+ for concatenation
std::string_view sv2 = " World";
std::string sv3 = sv + sv2; // C++23 supports concatenation of string_view with string_view/string literals
std::cout << sv3 << std::endl; // Outputs Hello C++23 World
// 3. Native UTF-8 string literals (type std::u8string_view)
using namespace std::literals::string_view_literals;
auto utf8_sv = u8"你好"sv; // Type std::u8string_view, clearly UTF-8 encoded
std::cout << "UTF-8 string length: " << utf8_sv.size() << std::endl; // Outputs 6 (each Chinese character is 3 bytes)
// 4. New starts_with/ends_with string literal overloads
if (sv.starts_with("Hello")) { // C++23 supports directly passing string literals, no need to convert to string_view
std::cout << "Starts with Hello" << std::endl;
}
return 0;
}
Template Syntax Simplification: auto(x) and Template Parameter Deduction
In C++20, template parameter deduction still required explicit type specification or complex syntax in certain scenarios (such as forwarding references, constant expressions); C++23 introduced auto(x) syntax for template parameter deduction, while simplifying the deduction rules for non-type template parameters, making template usage more concise.
// C++20 Implementation: Verbose scenarios for template parameter deduction
#include <iostream>
#include <type_traits>// Non-type template parameters, C++20 requires explicit type specification
template <typename T, T Val>struct Constant {
static constexpr T value = Val;};
// Forwarding reference template, C++20 deduction may lead to unexpected results
template <typename T>void func(T&& val) {
// Need to use std::forward to forward, and type checking is cumbersome
if (std::is_lvalue_reference_v<T>) {
std::cout << "Lvalue reference: " << val << std::endl;
} else {
std::cout << "Rvalue reference: " << val << std::endl;
}}
int main() {
// Non-type template parameters require explicit type specification
int Constant<int, 42> c1;
std::cout << c1.value << std::endl; // Outputs 42
// Forwarding reference usage, right value must be converted via std::move
int x = 10;
func(x); // Correct, deduced as lvalue reference
func(std::move(x)); // Correct, deduced as rvalue reference
// func(20); // Correct, but in C++20 if passing constant expressions, no simplified syntax
return 0;
}
// C++23 Implementation: Template syntax simplification
#include <iostream>
#include <type_traits>// Non-type template parameters, C++23 supports auto deduction of type
template <auto Val> // No need to explicitly specify T, auto deduces the type of Val
struct Constant {
static constexpr auto value = Val;};
// Use auto(x) to simplify forwarding reference logic (new in C++23)
template <typename T>void func(T val) {
// auto(x) can maintain value category, simplifying forwarding logic
std::cout << "Value: " << val << std::endl;}
// Constant expression template parameter simplification
template <std::integral auto Val> // Constrain Val to be an integer type, auto deduces specific type
constexpr auto square() {
return Val * Val;}
int main() {
// Non-type template parameters automatically deduce type, no need for explicit specification
Constant<42> c1; // Deduced as Constant<int, 42>
Constant<3.14> c2; // Deduced as Constant<double, 3.14>
std::cout << c1.value << " " << c2.value << std::endl; // Outputs 42 3.14
// Forwarding reference simplified, no need for std::forward, directly pass
int x = 10;
func(x); // Pass lvalue, val is int
func(std::move(x)); // Pass rvalue, val is int (can adjust to reference as needed)
func(20); // Pass constant, val is int
// Constant expression template call simplification
constexpr auto s1 = square<5>(); // Deduced as int, returns 25
constexpr auto s2 = square<10ul>(); // Deduced as unsigned long, returns 100
std::cout << s1 << " " << s2 << std::endl; // Outputs 25 100
return 0;
}