Core Features Analysis of C++ Version Evolution: From C++98 to C++23

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&amp; 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&amp; [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&amp; path) {
    try {
        // Unified interface for traversing directories, effective on Windows/Linux/Mac
        for (const auto&amp; entry : fs::directory_iterator(path)) {
            // Get file name and output
            std::cout << entry.path().filename() << std::endl;
        }
    } catch (const fs::filesystem_error&amp; 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&amp; path, std::string&amp; 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&amp; 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&amp; 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&amp; 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&amp;&amp; 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;
}

Leave a Comment