Using C++20 and C++23
Using C++20 and C++23
Start adopting modern language and library features that pay off quickly.
Using C++20 and C++23
Start adopting modern language and library features that pay off quickly.
std::span for non-owning array viewsstd::jthread for safer thread lifetime managementstd::expected for explicit success-or-error return valuesThese are good first targets because they usually improve readability immediately without demanding a full architecture rewrite.
std::spanvoid print_all(std::span<const int> values) {
for (int value : values) {
std::cout << value << '\n';
}
}
std::span lets one function accept arrays, vectors, and other contiguous ranges without taking ownership.
template <typename T>
concept Printable = requires(T value) {
std::cout << value;
};
Concepts turn implicit template assumptions into explicit API rules, which is one of the highest-value changes in modern generic code.
Add modern features where they improve readability immediately. Do not rewrite stable code just to chase newer syntax.
std::spanvoid process(std::span<const std::byte> bytes);
std::jthreadstd::jthread worker([](std::stop_token token) {
while (!token.stop_requested()) {
poll_once();
}
});
std::expectedstd::expected<int, std::string> parse_count(std::string_view text) {
if (text.empty()) {
return std::unexpected("empty input");
}
return 42;
}
std::optional, std::variant, and std::expected for value-based APIsstd::format and std::print for cleaner text outputstd::source_location for diagnostics without manual file and line plumbingstd::chrono calendar/time-zone facilities when you need real-world dates and timesstruct Version {
int major{};
int minor{};
auto operator<=>(const Version&) const = default;
};
With a defaulted <=>, the compiler can synthesize the normal relational operators for you. This is one of the cleaner modern features because it removes repetitive comparison boilerplate.
std::source_locationvoid log_message(std::string_view text,
std::source_location where = std::source_location::current()) {
std::cout << where.file_name() << ':' << where.line() << " " << text << '\n';
}
This is a practical upgrade over hand-passing file and line macros through your own logging APIs.
std::span, std::string_viewstd::jthreadstd::format, std::source_locationstd::expectedThe cleanest rollout is usually bottom-up.
std::span, std::string_view, and [[nodiscard]]std::jthread, std::expected, or std::format where they reduce real friction in services, parsing, logging, or worker codeThis keeps the early changes cheap and local instead of mixing low-risk upgrades with toolchain-heavy ones.
void process(const int* values, std::size_t count);
void process(std::span<const int> values);
std::thread worker(run);
worker.join();
std::jthread worker(run);
std::expectedstd::expected<int, std::string> parse_count(std::string_view text) {
if (text.empty()) {
return std::unexpected("empty input");
}
return 42;
}
The point is not that expected replaces every exception. It gives you a strong option when failure is part of normal control flow and callers should branch on the result directly.
These two features are real parts of modern C++, but they require more build-system and library support than features like std::span or concepts. Adopt them deliberately rather than mechanically.
expected does and does not fitstd::expected works best when callers are supposed to branch locally on failure.
It is usually a worse fit when every caller would just forward the error upward, or when the failure is exceptional enough that unwinding is the clearer design.
That means expected and exceptions often coexist in the same codebase. Use each where its calling pattern is natural.
std::string_view, std::span, [[nodiscard]]std::formatstd::jthread, std::expected, std::source_locationstd::span.std::expected.std::jthread.#include <format>
#include <string>
int main() {
std::string text = std::format("value = {}", 42);
return static_cast<int>(text.size());
}
Swap one older pattern for a newer standard facility, then compare clarity. This keeps modern C++ grounded in practical tradeoffs instead of feature tourism.
#include <format>
#include <string>
int main() {
std::string line = std::format("{} items ready", 3);
return static_cast<int>(line.size());
}