Skip to content

C++ Standards in Practice

C++ evolves in three-year standard cycles, but implementation is incremental. A label such as C++20 identifies a language/library contract, not a promise that every compiler and platform ships every component.

C++98 and C++03 define the legacy language

These editions established templates, exceptions, the original standard containers and algorithms, and the classic object model. Maintained code may use raw owning pointers, iterator-heavy loops, typedef, and preprocessor techniques because later alternatives did not exist.

Recognize the style without reproducing it unnecessarily in new code.

C++11 changed the default design vocabulary

C++11 introduced move semantics, rvalue references, auto, lambdas, range-based for, nullptr, scoped enums, variadic templates, smart pointers, concurrency, constexpr, uniform initialization, and defaulted/deleted functions.

This is the boundary between “classic” and broadly modern C++. The language became much more capable of expressing ownership and generic code directly.

C++14 and C++17 made modern code smoother

C++14 generalized lambdas and relaxed constexpr, among many refinements. C++17 added structured bindings, if constexpr, fold expressions, inline variables, string_view, optional, variant, any, and filesystem support.

These versions are common compatibility baselines for libraries that cannot yet require C++20.

C++20 added concepts, ranges, coroutines, and modules

C++20 is another large revision. Concepts constrain templates; ranges add composable algorithms and views; coroutines provide language machinery for library-defined async/generator abstractions; modules offer an alternative to textual headers; jthread, span, formatting facilities, and many other updates expand the library.

Adoption differs sharply. Concepts and span are broadly straightforward. Modules depend on compiler, build-system, dependency-scanning, and package workflows. Coroutines require an abstraction/library that defines scheduling and ownership; the keyword alone is not an async runtime.

C++23 continues library and language refinement

C++23 adds facilities such as std::expected, std::print, multidimensional subscript improvements, deducing this, more ranges support, and extensive constexpr and library work. Availability is component-specific. Check compiler and standard-library matrices separately.

C++26 work may be described for orientation, but a course should not teach an unreleased or incompletely deployed feature as a production baseline. Revisit this statement as standards and toolchains ship.

Select and detect deliberately

sh
c++ -std=c++20 source.cpp

MSVC uses /std:c++20; CMake expresses the requirement through target compile features:

cmake
target_compile_features(report PRIVATE cxx_std_20)

Feature-test macros such as __cpp_concepts and library macros from <version> support narrow conditional code. Prefer build-time requirements over a forest of fallback branches when all supported targets can upgrade.

Avoid using standard versions as quality labels

A good C++17 design can be safer than careless C++23. New facilities reduce boilerplate and encode intent, but ownership, lifetime, error policy, and tests still decide quality.

Upgrade in two phases: first make the existing code compile and behave under the new mode; then adopt features in reviewable changes. Test warnings and ABI implications across supported compilers.

Optional prompts

Explain: Why was C++11 more than a collection of syntax conveniences?

Answer: Move semantics, RAII-supporting library types, lambdas, concurrency, and stronger language constructs changed how ownership, generic code, and APIs are normally designed.

Explain: Why can modules be harder to adopt than concepts?

Answer: Modules affect dependency discovery, compilation order, build metadata, and package distribution across the whole toolchain, while concepts are primarily a front-end language feature.

Further reference