crtp_modern.cpp
· 2.9 KiB · C++
Raw
// crtp_modern.cpp — compile with: c++ -std=c++20 -O2 crtp_modern.cpp
//
// CRTP (Curiously Recurring Template Pattern) shines when a base class needs
// to return or operate on the *derived* type without paying for virtual
// dispatch. Two practical mixins below.
#include <concepts>
#include <iostream>
#include <string>
#include <utility>
// ---------------------------------------------------------------------------
// 1. Fluent-builder mixin
//
// Problem: chained setters in a base class return `Base&`, which loses the
// derived type and breaks further chained calls to derived methods.
// CRTP fix: the base knows the derived type and casts `*this` to it.
// ---------------------------------------------------------------------------
template <class Derived>
class FluentBase {
public:
Derived& self() noexcept { return static_cast<Derived&>(*this); }
Derived& named(std::string n) & {
name_ = std::move(n);
return self();
}
const std::string& name() const noexcept { return name_; }
private:
std::string name_;
};
class HttpRequest : public FluentBase<HttpRequest> {
public:
HttpRequest& method(std::string m) { method_ = std::move(m); return *this; }
HttpRequest& url(std::string u) { url_ = std::move(u); return *this; }
void send() const {
std::cout << '[' << name() << "] " << method_ << ' ' << url_ << '\n';
}
private:
std::string method_, url_;
};
// ---------------------------------------------------------------------------
// 2. Arithmetic mixin
//
// Define `+=`, `-=`, `*=` in your type; the mixin synthesizes `+`, `-`, `*`
// returning the derived type. No virtuals, no duplicated boilerplate per type.
// ---------------------------------------------------------------------------
template <class Derived>
struct Arithmetic {
friend Derived operator+(Derived a, const Derived& b) { a += b; return a; }
friend Derived operator-(Derived a, const Derived& b) { a -= b; return a; }
friend Derived operator*(Derived a, const Derived& b) { a *= b; return a; }
};
struct Vec2 : Arithmetic<Vec2> {
double x{}, y{};
Vec2& operator+=(const Vec2& o) { x += o.x; y += o.y; return *this; }
Vec2& operator-=(const Vec2& o) { x -= o.x; y -= o.y; return *this; }
Vec2& operator*=(const Vec2& o) { x *= o.x; y *= o.y; return *this; }
friend std::ostream& operator<<(std::ostream& os, const Vec2& v) {
return os << '(' << v.x << ", " << v.y << ')';
}
};
// ---------------------------------------------------------------------------
// Demo
// ---------------------------------------------------------------------------
int main() {
HttpRequest{}
.named("health-check") // FluentBase method, returns HttpRequest&
.method("GET") // derived method still reachable
.url("https://example.com/health")
.send();
Vec2 a{1.0, 2.0}, b{3.0, 4.0};
std::cout << a + b << " | " << a * b << '\n';
}
| 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 | } |
| 86 |