Last active 2 months ago

Two practical CRTP use cases in C++20 — preserving derived type in chained setters, and synthesizing arithmetic operators once.

pb-gist revised this gist 2 months ago. Go to revision

1 file changed, 85 insertions

crtp_modern.cpp(file created)

@@ -0,0 +1,85 @@
1 + // crtp_modern.cpp — compile with: c++ -std=c++20 -O2 crtp_modern.cpp
2 + //
3 + // CRTP (Curiously Recurring Template Pattern) shines when a base class needs
4 + // to return or operate on the *derived* type without paying for virtual
5 + // dispatch. Two practical mixins below.
6 +
7 + #include <concepts>
8 + #include <iostream>
9 + #include <string>
10 + #include <utility>
11 +
12 + // ---------------------------------------------------------------------------
13 + // 1. Fluent-builder mixin
14 + //
15 + // Problem: chained setters in a base class return `Base&`, which loses the
16 + // derived type and breaks further chained calls to derived methods.
17 + // CRTP fix: the base knows the derived type and casts `*this` to it.
18 + // ---------------------------------------------------------------------------
19 + template <class Derived>
20 + class FluentBase {
21 + public:
22 + Derived& self() noexcept { return static_cast<Derived&>(*this); }
23 +
24 + Derived& named(std::string n) & {
25 + name_ = std::move(n);
26 + return self();
27 + }
28 +
29 + const std::string& name() const noexcept { return name_; }
30 +
31 + private:
32 + std::string name_;
33 + };
34 +
35 + class HttpRequest : public FluentBase<HttpRequest> {
36 + public:
37 + HttpRequest& method(std::string m) { method_ = std::move(m); return *this; }
38 + HttpRequest& url(std::string u) { url_ = std::move(u); return *this; }
39 +
40 + void send() const {
41 + std::cout << '[' << name() << "] " << method_ << ' ' << url_ << '\n';
42 + }
43 +
44 + private:
45 + std::string method_, url_;
46 + };
47 +
48 + // ---------------------------------------------------------------------------
49 + // 2. Arithmetic mixin
50 + //
51 + // Define `+=`, `-=`, `*=` in your type; the mixin synthesizes `+`, `-`, `*`
52 + // returning the derived type. No virtuals, no duplicated boilerplate per type.
53 + // ---------------------------------------------------------------------------
54 + template <class Derived>
55 + struct Arithmetic {
56 + friend Derived operator+(Derived a, const Derived& b) { a += b; return a; }
57 + friend Derived operator-(Derived a, const Derived& b) { a -= b; return a; }
58 + friend Derived operator*(Derived a, const Derived& b) { a *= b; return a; }
59 + };
60 +
61 + struct Vec2 : Arithmetic<Vec2> {
62 + double x{}, y{};
63 +
64 + Vec2& operator+=(const Vec2& o) { x += o.x; y += o.y; return *this; }
65 + Vec2& operator-=(const Vec2& o) { x -= o.x; y -= o.y; return *this; }
66 + Vec2& operator*=(const Vec2& o) { x *= o.x; y *= o.y; return *this; }
67 +
68 + friend std::ostream& operator<<(std::ostream& os, const Vec2& v) {
69 + return os << '(' << v.x << ", " << v.y << ')';
70 + }
71 + };
72 +
73 + // ---------------------------------------------------------------------------
74 + // Demo
75 + // ---------------------------------------------------------------------------
76 + int main() {
77 + HttpRequest{}
78 + .named("health-check") // FluentBase method, returns HttpRequest&
79 + .method("GET") // derived method still reachable
80 + .url("https://example.com/health")
81 + .send();
82 +
83 + Vec2 a{1.0, 2.0}, b{3.0, 4.0};
84 + std::cout << a + b << " | " << a * b << '\n';
85 + }
Newer Older