conceptModern C++~5 min readUpdated 2026-07-02#cpp#cpp20#cpp23#standard-library#systems

Modern C++ (11 → 23) highlights worth adopting

Modern C++ is not one feature. It is a pressure to make ownership explicit, pass views instead of raw pointer pairs, constrain templates before they explode, and push boring bookkeeping into types. The useful subset is the part that makes the code more local, more checkable, and easier for the optimizer to erase. The dangerous subset is the part that makes runtime allocation, dispatch, lifetime, or build complexity invisible.

The reset: adopt modern C++ as vocabulary for invariants, not as decoration. A feature is worth keeping when it makes the generated code, ownership boundary, or error path easier to reason about.

How it really works

C++11 was the big reset: move semantics, lambdas, auto, nullptr, range-for, constexpr, enum class, smart pointers, threads, and the first wave of vocabulary types. C++14 and C++17 made the reset livable with generic lambdas, better constexpr, std::optional, std::variant, std::string_view, structured bindings, if constexpr, filesystem, and parallel algorithm hooks. C++20 added concepts, ranges, std::span, std::jthread, coroutines, std::source_location, spaceship comparison, modules, and stronger compile-time tools. C++23 filled in important library gaps such as std::expected, more ranges, std::to_underlying, and improvements to constexpr and vocabulary types.

The implementation story is familiar from the rest of this atlas:

Feature family What it gives you Low-level reality
Move semantics transfer ownership or buffers without copying moved-from objects still exist and must be valid
RAII and smart pointers automatic cleanup on every scope exit destructor calls are real code at lifetime boundaries
Lambdas and templates visible callable types for inlining header visibility and code size matter
std::optional and std::variant explicit maybe/one-of states object layout grows to hold tags and alternatives
std::span and std::string_view pointer-plus-size views no ownership and usually no bounds checks
Concepts readable template requirements compile-time checking, not a runtime guard
Ranges composable iteration pipelines laziness and iterator categories affect generated loops
std::expected value-or-error return type C++23 availability depends on toolchain support

Use feature-test macros and compiler support tables when you cross the C++20/C++23 boundary. A language feature being standardized does not mean your project compiler, standard library, sanitizer setup, or embedded/freestanding target has it.

Executable artifact: a C++20 vocabulary slice

The runnable demo lives in examples/modern-cpp/modern-cpp-11-to-23-highlights/. It uses a C++20 subset that maps well to systems code: std::span, std::optional, std::variant, concepts, structured bindings, lambdas, and constexpr.

cd examples/modern-cpp/modern-cpp-11-to-23-highlights
./run.sh

The constrained function says what kind of object it accepts before template instantiation turns into a wall of diagnostics:

template <typename T>
concept HasCycleCount = requires(T const& item) {
    { item.name } -> std::convertible_to<std::string_view>;
    { item.cycles } -> std::convertible_to<int>;
};

template <HasCycleCount T>
int total_cycles(std::span<T const> samples)
{
    return std::accumulate(samples.begin(), samples.end(), 0, [](int total, T const& sample) {
        return total + sample.cycles;
    });
}

The script also emits demo.O2.s. In that assembly, concepts do not appear as runtime checks; they constrained the template at compile time. std::span is passed as view metadata, the lambda body is visible to the optimizer, and the remaining runtime facts come from the actual control flow: the optional budget check and the variant selection.

What to adopt first

  • nullptr, enum class, auto when the type is obvious, and range-for remove whole categories of accidental C compatibility traps.
  • RAII, std::unique_ptr, std::make_unique, and scoped locks make cleanup a property of lifetime instead of a convention in every return path.
  • Move-only types let APIs express ownership transfer without comments.
  • std::span<T> and std::string_view replace pointer-plus-length parameters when the callee does not own the data.
  • std::optional<T> is good for "maybe a value"; it is not enough when you need to know why the value is absent.
  • std::variant is good for a closed set of alternatives; it should usually be visited close to where the alternatives are meaningful.
  • Concepts are worth adopting for public generic APIs and template-heavy internals because they move failures to named constraints.
  • std::expected<T, E> is worth adopting when your toolchain supports C++23 and the failure is ordinary, local, and expected by callers.
  • std::source_location replaces many logging macros with a typed default parameter.

Failure modes & trade-offs

  • Chasing the newest standard before the toolchain is ready. The language mode, standard library, sanitizer runtime, and target platform all have to line up.
  • Using vocabulary types without a policy. optional, variant, expected, exceptions, and assertions each mean different things. If a codebase treats them interchangeably, readers lose the signal.
  • Turning views into dangling references. std::span and std::string_view do not extend lifetime. Returning a view into a local object is just a prettier use-after-free.
  • Hiding allocation behind convenience. std::string, std::vector, std::function, coroutines, and ranges can allocate or grow state depending on how they are used.
  • Template code size. Concepts improve diagnostics, but every concrete template instantiation still contributes to compile time and possibly binary size.
  • Assuming release and debug behave similarly. Debug STL modes, missed inlining, and iterator checks can change the profile dramatically.

In practice

Pick a project baseline before you pick features. For new hosted systems code in 2026, C++20 is a practical floor and C++23 is a feature-by-feature adoption target. For kernels, bootloaders, firmware, or cross-compiled OS work, the baseline is whatever your freestanding headers, runtime, ABI, and build chain can actually support.

Use the "mechanism test" for each feature. Ask what object lifetime it creates, what data layout it implies, whether it can allocate, whether it can throw, whether it crosses an ABI boundary, and what the optimizer can see. If you can answer those questions, modern C++ is still low-level programming. If you cannot, the abstraction is leading.

Connects to: Move semantics and value categories · RAII: the idea that changes everything · Templates and generic programming · The STL: containers, iterators, algorithms · Error handling: exceptions vs expected · Which subset of C++ to use · Why read assembly? Compiler Explorer

Sources

  • Draft C++ Standard - current working draft for checking exact language and library wording when summaries disagree. https://eel.is/c++draft/
  • cppreference - C++ compiler support - practical table for feature availability across compilers and standard libraries. https://en.cppreference.com/w/cpp/compiler_support
  • cppreference - Constraints and concepts - mechanism behind named template requirements and constrained overloads. https://en.cppreference.com/w/cpp/language/constraints
  • cppreference - std::span - details the pointer-plus-extent view and its lifetime implications. https://en.cppreference.com/w/cpp/container/span
  • cppreference - Ranges library - reference for C++20 range algorithms, views, and iterator-category constraints. https://en.cppreference.com/w/cpp/ranges
  • cppreference - std::source_location - typed call-site diagnostics introduced in C++20. https://en.cppreference.com/w/cpp/utility/source_location
  • C++ Core Guidelines - broad design guidance for resource management, interfaces, and modern C++ style. https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines
  • Compiler Explorer - inspect whether a modern abstraction lowered to the loop, call, or object layout you expected. https://godbolt.org/