modern-cpp
Skill1 starsChanged 7 months agoMITView on GitHub
Best practices and idioms for writing modern C++ (C++20, C++23, C++26). Use this skill whenever the user asks for help writing C++ code, reviewing C++ code, designing C++ APIs, or converting legacy C/C++ to modern style. Also trigger when the user mentions smart pointers, RAII, modules (.cppm/.ixx), concepts, ranges, coroutines, constexpr, consteval, constinit, designated initializers, deducing this, std::expected, std::optional, std::format, std::print, reflection, contracts, move semantics, value categories, templates, or any C++20/23/26 feature. Trigger liberally for anything involving C++ — even casual questions like "how should I structure this C++ class?" or "is there a better way to write this?" warrant consulting this skill.
3 other projects use a version of this.
Adopted by
Most recently taken up by
- nodetool-ai/nodetooltheir own version, first seen Nov 8, 2026
- meowdiocre/meowpitheir own version, first seen Oct 27, 2026
- mordechai30/cpp_skillstheir own version, first seen Oct 8, 2026
Other versions3 projects use a version of this.
Word for word · 0
None found.
Edited · 0
None found.
Wrote their own version · 3
- nodetool-ai/nodetool★ 615same name
- meowdiocre/meowpi★ 3same name
- mordechai30/cpp_skills★ 0same name
How matches are found. Word for word: the exact same text. Edited: shares passages with this file (“99% same” means 99% of its five-word phrases also appear here; 10% or more counts). Their own version: similar meaning, or, weakest, the same name. We can’t tell which project had it first. Projects are counted among the 200 most-starred candidates, and mentions are links to the repository we found, up to 50 per site, so both counts are a floor.
In this repository3 other files it gives its agents.
The file
Show its rules and text
We split this skill into the rules it gives an agent
20 rules. 0 are common, 5 are distinctive, and 15 are very specific to this project.
0
Common
Most of the 3 edited versions we read keep these.
5
Distinctive
This author's own choices. Worth a look.
15
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 24
Ownership is explicit: Every resource has a clear owner.
- Very specific: line 32
Compile-time over runtime: Prefer constexpr/consteval computation, concepts for type constraints, and static_assert for invariants.
- Very specific: line 36
No naked
new/delete: Use std::make_unique, std::make_shared, or containers. - Very specific: line 84
Create with std::make_unique<T>(args...).
- Very specific: line 93
Rule of Zero: if your class only holds RAII members (smart pointers, containers, std::string, etc.), don't write a destructor, copy/move …
- Distinctive: line 98
Rule of Five: if you must manage a resource manually, implement *all five*: destructor, copy constructor, copy assignment, move …
- Very specific: line 125
Use requires clauses for ad-hoc constraints on functions or classes.
- Very specific: line 171
Use import std; (C++23) to import the entire standard library as a single module — this is typically faster than #include chains.
- Distinctive: line 188
Use designated initializers for aggregate types to improve readability and resilience to member reordering:
- Very specific: line 235
Return-type rule of thumb: use decltype(auto) (not auto&) and always write std::forward<Self>(self).member in the return expression.
- Very specific: line 275
Prefer std::expected<T, E> (C++23) for operations that can fail with a specific error type.
- Very specific: line 282
Use std::optional<T> when absence of a value is a normal (non-error) case.
- Very specific: line 311
Contracts: add [[pre:]], [[post:]], and contract_assert for design-by-contract:
- Distinctive: line 332
Follow the project's existing style when contributing.
- Very specific: line 333
auto: use when the type is obvious from context or unimportant. - Very specific: line 335
Braces: always use braces for if/for/while bodies, even single lines.
- Very specific: line 362
For new code: write C++20 as baseline. Reach for C++23 features (std::expected, std::print, deducing this, if consteval) when the toolchain …
- Very specific: line 372
For API design: prefer value types, use std::expected for fallible operations, constrain templates with concepts, and use designated …
- Distinctive: line 376
For explaining features: use concise examples. Show the "before" (legacy) and "after" (modern) patterns.
- Distinctive: line 379
When in doubt, consult the reference files for deeper guidance:
“Common” is measured: kept by at least 6 in 10 of the 3 edited versions of this file we could read. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Modern C++ Programming Skill
This skill guides Claude to produce idiomatic, safe, and performant C++ targeting C++20 as the baseline, with C++23 and C++26 features used when appropriate. The philosophy: write code that is correct by construction — leverage the type system, RAII, and compile-time checks so bugs are caught before runtime.
Guiding Principles
- Ownership is explicit. Every resource has a clear owner. Express ownership
through value semantics,
std::unique_ptr, or — only when truly shared —std::shared_ptr. Raw pointers mean "non-owning observer" and nothing else. - Prefer value semantics. Pass and return objects by value when they are cheap to copy or when move semantics make it efficient. Avoid heap allocation when the stack suffices.
- Compile-time over runtime. Prefer
constexpr/constevalcomputation, concepts for type constraints, andstatic_assertfor invariants. Push errors to compile time wherever possible. - No naked
new/delete. Usestd::make_unique,std::make_shared, or containers. If you need custom allocation, wrap it in an RAII type. - Minimal includes, prefer modules. For new C++20+ projects, prefer
import std;or granular module imports over#includewhen the build system supports it. - Use the standard library first. Reach for
<algorithm>,<ranges>,<format>,<expected>,<optional>,<variant>,<span>, etc. before writing custom utilities.
Quick Reference: What to Use When
| Legacy pattern | Modern replacement | Standard |
new T / delete | std::make_unique<T>() / std::make_shared<T>() | C++14+ |
NULL / 0 | nullptr | C++11 |
#include <header> | import std; or import <header>; | C++20/23 |
typedef | using Alias = Type; | C++11 |
| Error codes / exceptions only | std::expected<T, E> | C++23 |
| Nullable pointer for optional | std::optional<T> | C++17 |
printf / iostream formatting | std::format / std::print | C++20/23 |
| SFINAE for template constraints | concept + requires | C++20 |
| Macro constants | constexpr / consteval / constinit | C++11-20 |
const_cast overload duplication | Deducing this | C++23 |
| CRTP boilerplate | Deducing this | C++23 |
| Positional aggregate init | Designated initializers | C++20 |
| Raw loops over containers | Ranges + views | C++20 |
| Manual enum-to-string | Static reflection (^, [:..:]) | C++26 |
Core Topics
For detailed guidance on each area, read the corresponding reference file in
references/. Below is a summary of when and how to apply each topic.
1. RAII & Smart Pointers
Read: references/raii-and-ownership.md
RAII ties resource lifetime to object lifetime. Every resource — memory, files, locks, sockets — should be managed by an RAII wrapper. The standard library provides the key RAII types:
std::unique_ptr<T>— sole ownership, zero overhead. Default choice. Create withstd::make_unique<T>(args...). Transfer withstd::move.std::shared_ptr<T>— shared ownership via reference counting. Use only when multiple owners genuinely need independent lifetimes. Create withstd::make_shared<T>(args...).std::weak_ptr<T>— non-owning observer of ashared_ptr. Use to break cycles (e.g., parent↔child in a graph).- Custom RAII wrappers — for non-memory resources, use
unique_ptrwith a custom deleter, or write a small class following the Rule of Zero/Five.
Rule of Zero: if your class only holds RAII members (smart pointers,
containers, std::string, etc.), don't write a destructor, copy/move
constructors, or assignment operators — the compiler-generated defaults are
correct.
Rule of Five: if you must manage a resource manually, implement all five: destructor, copy constructor, copy assignment, move constructor, move assignment.
When raw pointers are OK: as non-owning observers passed to functions that
don't store the pointer. Use T* for nullable observers, T& for non-nullable.
std::span<T> and std::string_view are preferred for ranges of data.
2. Concepts & Constrained Templates (C++20)
Read: references/concepts-and-templates.md
Concepts replace SFINAE with readable, composable constraints:
template <std::integral T>
T gcd(T a, T b) { return b == 0 ? a : gcd(b, a % b); }Define custom concepts when standard ones don't fit:
template <typename T>
concept Serializable = requires(T t, std::ostream& os) {
{ t.serialize(os) } -> std::same_as<void>;
};Use requires clauses for ad-hoc constraints on functions or classes. Prefer
named concepts for reusability and error message clarity.
3. Ranges & Views (C++20/23)
Read: references/ranges-and-views.md
Ranges enable pipeline-style data processing with lazy evaluation:
auto results = data
| std::views::filter([](auto& x) { return x.active; })
| std::views::transform(&Item::name)
| std::views::take(10);C++23 adds std::views::chunk, std::views::slide, std::views::zip,
std::views::enumerate, and fold algorithms. Prefer range-based algorithms
(std::ranges::sort, std::ranges::find_if) over their <algorithm>
predecessors — they accept ranges directly and offer projection parameters.
4. Modules (C++20/23)
Read: references/modules.md
Modules replace #include with import, eliminating redundant parsing and
macro leakage. Key file conventions:
.cppm(Clang) /.ixx(MSVC) — module interface unit.cpp— module implementation unit
A minimal module:
// math.cppm — module interface
export module math;
export int add(int a, int b) { return a + b; }// main.cpp — consumer
import math;
int main() { return add(1, 2); }Use import std; (C++23) to import the entire standard library as a single
module — this is typically faster than #include chains.
Partition modules for large libraries:
// mylib-core.cppm
export module mylib:core;
export class Engine { /* ... */ };
// mylib.cppm — primary interface
export module mylib;
export import :core;5. Designated Initializers (C++20)
Use designated initializers for aggregate types to improve readability and resilience to member reordering:
struct Config {
int width = 1920;
int height = 1080;
bool fullscreen = false;
int samples = 4;
};
auto cfg = Config{ .width = 2560, .height = 1440, .fullscreen = true };
// .samples keeps its default value of 4Rules to remember:
- Designators must appear in declaration order (unlike C99).
- Cannot mix designated and positional initializers.
- Unlisted members use their default member initializers (or zero-init).
This pattern works especially well as a substitute for named function parameters by passing a config struct:
Texture create_texture(TextureDesc desc);
auto tex = create_texture({ .format = Format::RGBA8, .width = 512, .height = 512 });6. Deducing this / Explicit Object Parameter (C++23)
Read: references/deducing-this.md
C++23 lets you make the implicit this parameter explicit, eliminating the need
to duplicate member functions for different cv/ref qualifiers:
struct Widget {
// One function handles &, const&, and && — no duplication
template <class Self>
decltype(auto) name(this Self&& self) {
return std::forward<Self>(self).name_;
}
private:
std::string name_;
};Return-type rule of thumb: use decltype(auto) (not auto&) and always
write std::forward<Self>(self).member in the return expression. auto&
collapses to an lvalue reference and can't carry an rvalue-qualified call
through; an unforwarded self.member is always an lvalue regardless of how
Self was deduced. Skip the forward only when there's no value category to
propagate (e.g. returning a std::span built from pointer + size).
For chained calls to another deducing-this template member, use
.template: std::forward<Self>(self).template inner<T>().method() — and
forward the outermost object, not the intermediate call.
Key use cases:
- De-quadruplication: one templated overload replaces up to four (const/non-const × lvalue/rvalue).
- Recursive lambdas:
auto fact = [](this auto self, int n) -> int { return n < 2 ? 1 : n * self(n-1); }; - Simplified CRTP: base class no longer needs to be templated on the derived type.
Restrictions: explicit object parameters cannot be virtual, static, or have
cv/ref qualifiers on the function itself. See
references/deducing-this.md section 4 ("Return Types and Forwarding") for
the detailed rules and a pre-commit checklist.
7. constexpr, consteval, constinit (C++11–C++23)
Read: references/compile-time-programming.md
The constexpr family pushes computation to compile time:
constexpr— may run at compile time if all inputs are constant expressions. In C++20 expanded to virtual functions,try/catch,dynamic_cast. In C++23 further expanded (e.g.,constexpr std::unique_ptr,constexpr std::optional).consteval— must run at compile time (immediate function). Use for functions that should never appear in runtime code (lookup tables, hashing, etc.).constinit— ensures a variable is initialized at compile time but does not make itconst. Solves the Static Initialization Order Fiasco.if consteval(C++23) — branch based on whether code is being constant-evaluated, replacingstd::is_constant_evaluated().
8. Error Handling: std::expected & std::optional (C++17/23)
Prefer std::expected<T, E> (C++23) for operations that can fail with a
specific error type. It makes the error path explicit in the type system:
std::expected<Config, ParseError> parse_config(std::string_view path);Use std::optional<T> when absence of a value is a normal (non-error) case.
Both support monadic operations (.and_then, .transform, .or_else) for
clean chaining in C++23.
Reserve exceptions for truly exceptional situations (I/O failures, out-of-memory, programming errors). Avoid exceptions in hot paths.
9. Modern Formatting: std::format & std::print (C++20/23)
Replace printf and iostream formatting with std::format / std::print:
std::println("Hello, {}! You scored {:.1f}%", name, score);std::format returns a std::string; std::print/std::println write
directly to stdout (C++23), avoiding temporary allocations. Both use Python-like
format strings and are type-safe.
10. C++26 Preview: Reflection & Contracts
Read: references/cpp26-preview.md
Static reflection (the biggest C++26 feature) enables compile-time introspection and code generation:
^T— the reflection operator, yields astd::meta::infovalue[:refl:]— the splice operator, turns reflection back into codestd::meta::members_of,std::meta::name_of, etc. — query reflected entities
Contracts add [[pre:]], [[post:]], and contract_assert for
design-by-contract:
int safe_divide(int a, int b)
[[pre: b != 0]]
[[post r: r * b == a]] // r names the return value
{
return a / b;
}These are draft features — compiler support is emerging. Use them in new projects when your toolchain supports them.
Code Style Conventions
- Naming:
snake_casefor functions, variables, namespaces;PascalCasefor types and concepts;UPPER_SNAKEonly for macros (which should be rare). Follow the project's existing style when contributing. auto: use when the type is obvious from context or unimportant; spell out the type when it aids readability.- Braces: always use braces for
if/for/whilebodies, even single lines. constby default: make variables, parameters, and member functionsconstunless mutation is needed.- Namespaces: wrap library code in namespaces. Never
using namespace std;in headers. In.cppfiles, prefer targetedusingdeclarations. - Includes / imports: group and sort. Prefer
importwhere supported.
Tooling Recommendations
- Compiler: GCC 13+, Clang 17+, or MSVC 2022 17.6+ for solid C++23 support. For C++26 reflection experiments, use the Bloomberg Clang/P2996 fork or latest GCC trunk.
- Build system: CMake 3.28+ with
set(CMAKE_CXX_STANDARD 23). For modules, CMake 3.28+ has experimental support; Clang's--precompile+scan-depsworkflow is the most mature. - Static analysis: enable
clang-tidywithmodernize-*,cppcoreguidelines-*,bugprone-*, andperformance-*checks. Use-Werrorin CI. - Sanitizers: compile with
-fsanitize=address,undefinedduring development. - Formatter:
clang-formatwith a project-wide.clang-formatconfig.
How to Use This Skill
- For new code: write C++20 as baseline. Reach for C++23 features
(
std::expected,std::print, deducingthis,if consteval) when the toolchain supports them. Mention C++26 features as forward-looking alternatives when relevant. - For code review / modernization: identify legacy patterns (raw
new/delete,typedef, SFINAE,#defineconstants, raw loops) and suggest modern replacements from the table above. Be incremental — don't rewrite everything at once. - For API design: prefer value types, use
std::expectedfor fallible operations, constrain templates with concepts, and use designated initializers for config/options structs. - For explaining features: use concise examples. Show the "before" (legacy) and "after" (modern) patterns. Mention compiler support status for newer features.
When in doubt, consult the reference files for deeper guidance:
references/raii-and-ownership.md— smart pointers, RAII patterns, Rule of Zero/Fivereferences/concepts-and-templates.md— writing and using concepts, requires clausesreferences/ranges-and-views.md— range adaptors, custom views, projectionsreferences/modules.md— module structure, partitions, build system integrationreferences/deducing-this.md— explicit object parameters, CRTP replacementreferences/compile-time-programming.md— constexpr family, compile-time containersreferences/cpp26-preview.md— reflection, contracts, pattern matching outlook
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

