Last active 2 months ago

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

crtp_modern.cpp Raw
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// ---------------------------------------------------------------------------
19template <class Derived>
20class FluentBase {
21public:
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
31private:
32 std::string name_;
33};
34
35class HttpRequest : public FluentBase<HttpRequest> {
36public:
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
44private:
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// ---------------------------------------------------------------------------
54template <class Derived>
55struct 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
61struct 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// ---------------------------------------------------------------------------
76int 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}
86