Skip to content
phookit.com
  1. Blog
  2. C++
  3. Modern C++ by example: six idioms you c…

Modern C++ by example: six idioms you can paste into a project today

Every example below is a complete program. Build with g++ -std=c++23 -Wall -Wextra file.cpp (GCC 14+) or the Clang/MSVC equivalent. Snippets are MIT licensed; copy freely.

1. Structured bindings: unpack pairs, tuples and structs

#include <map>
#include <print>
#include <string>

struct Point { double x, y; };

int main() {
    std::map<std::string, int> stock{{"bolts", 40}, {"nuts", 12}};
    for (const auto& [name, qty] : stock)
        std::println("{:<8} {}", name, qty);

    auto [inserted_it, ok] = stock.insert({"washers", 3});
    std::println("inserted={} name={}", ok, inserted_it->first);

    Point p{3.0, 4.0};
    auto [x, y] = p;
    std::println("dist={}", std::sqrt(x * x + y * y));
}

2. std::optional with monadic operations (C++23)

#include <charconv>
#include <optional>
#include <print>
#include <string_view>

std::optional<int> to_int(std::string_view s) {
    int v{};
    auto [ptr, ec] = std::from_chars(s.data(), s.data() + s.size(), v);
    if (ec == std::errc{} && ptr == s.data() + s.size()) return v;
    return std::nullopt;
}

int main() {
    for (std::string_view in : {"42", "x", "-7"}) {
        auto msg = to_int(in)
            .and_then([](int v) -> std::optional<int> { return v >= 0 ? std::optional{v * 2} : std::nullopt; })
            .transform([](int v) { return std::format("doubled: {}", v); })
            .value_or("not a non-negative integer");
        std::println("{:>3} -> {}", in, msg);
    }
}

3. Ranges: lazy pipelines that read top to bottom

#include <algorithm>
#include <print>
#include <ranges>
#include <vector>

struct Order { int id; double total; bool paid; };

int main() {
    std::vector<Order> orders{{1, 20.0, true}, {2, 55.5, false}, {3, 99.0, true}, {4, 5.0, true}};
    auto big_paid = orders
        | std::views::filter([](const Order& o) { return o.paid && o.total > 10; })
        | std::views::transform(&Order::id);
    for (int id : big_paid) std::print("{} ", id);        // 1 3
    std::println();

    std::ranges::sort(orders, std::ranges::greater{}, &Order::total);
    std::println("largest: #{}", orders.front().id);        // #3

    auto evens = std::views::iota(1) | std::views::filter([](int n) { return n % 2 == 0; }) | std::views::take(5);
    std::println("{}", std::ranges::to<std::vector>(evens)); // [2, 4, 6, 8, 10]
}

4. Concepts: constrain templates and get readable errors

#include <concepts>
#include <print>
#include <string>

template <typename T>
concept Shape = requires(const T& s) {
    { s.area() } -> std::convertible_to<double>;
    { s.name() } -> std::convertible_to<std::string>;
};

struct Circle { double r; double area() const { return 3.14159 * r * r; } std::string name() const { return "circle"; } };
struct Square { double s; double area() const { return s * s; } std::string name() const { return "square"; } };

void describe(const Shape auto& s) { std::println("{} with area {:.2f}", s.name(), s.area()); }

int main() {
    describe(Circle{1.0});
    describe(Square{2.0});
    // describe(42);   // error: 'int' does not satisfy 'Shape' - one line, not 200
}

5. std::variant + std::visit: a closed set of alternatives

#include <print>
#include <string>
#include <variant>
#include <vector>

struct Text { std::string body; };
struct Image { std::string alt; int width, height; };
struct Divider {};
using Block = std::variant<Text, Image, Divider>;

template <class... Ts> struct overloaded : Ts... { using Ts::operator()...; };

int main() {
    std::vector<Block> page{Text{"Hello"}, Image{"A cat", 800, 600}, Divider{}};
    for (const Block& b : page)
        std::visit(overloaded{
            [](const Text& t)  { std::println("<p>{}</p>", t.body); },
            [](const Image& i) { std::println("<img alt=\"{}\" width={} height={}>", i.alt, i.width, i.height); },
            [](Divider)        { std::println("<hr>"); },
        }, b);
}

Add a fourth alternative and every visit that doesn't handle it fails to compile, which is exactly what you want.

6. A ten-line scope guard

#include <print>
#include <utility>

template <class F>
class scope_exit {
    F f_; bool active_{true};
public:
    explicit scope_exit(F f) : f_(std::move(f)) {}
    ~scope_exit() { if (active_) f_(); }
    void release() { active_ = false; }
    scope_exit(const scope_exit&) = delete; scope_exit& operator=(const scope_exit&) = delete;
};

int main() {
    std::println("begin transaction");
    scope_exit rollback{[] { std::println("rollback"); }};
    // ... work ...
    bool ok = true;
    if (ok) { rollback.release(); std::println("commit"); }
}   // prints "rollback" only if we didn't release

This is the pattern behind std::experimental::scope_exit; until it lands in the standard library, this version is enough.

Comments 0

Log in or register to join the conversation.

No comments yet.