$npx -y skills add mohitmishra786/low-level-dev-skills --skill cpp-templatesC++ template skill for reading template errors and optimizing compile times. Use when deciphering template error stacks, setting -ftemplate-backtrace-limit, writing concepts and requires-clauses, understanding SFINAE vs concepts, or profiling template instantiation bottlenecks wi
| 1 | # C++ Templates |
| 2 | |
| 3 | ## Purpose |
| 4 | |
| 5 | Guide agents through reading and fixing template error messages, using concepts as cleaner constraints, understanding SFINAE vs concepts trade-offs, and profiling template instantiation depth and compile times with Templight. |
| 6 | |
| 7 | ## Triggers |
| 8 | |
| 9 | - "How do I read this massive C++ template error?" |
| 10 | - "How do I use concepts to constrain a template?" |
| 11 | - "What's the difference between SFINAE and concepts?" |
| 12 | - "My templates make compilation very slow" |
| 13 | - "How do I write a requires-clause?" |
| 14 | - "How do I profile template instantiation times?" |
| 15 | |
| 16 | ## Workflow |
| 17 | |
| 18 | ### 1. Reading template error messages |
| 19 | |
| 20 | Template errors print full instantiation chains. Strategy: read from the bottom up. |
| 21 | |
| 22 | ```text |
| 23 | prog.cpp:25:5: error: no matching function for call to 'sort' |
| 24 | std::sort(v.begin(), v.end()); |
| 25 | ^~~~~~~~~ |
| 26 | /usr/include/c++/13/bits/stl_algo.h:4869:5: note: candidate: |
| 27 | template<class _RAIter> |
| 28 | void std::sort(_RAIter, _RAIter) |
| 29 | note: template argument deduction/substitution failed: |
| 30 | prog.cpp:25:5: note: 'MyType' is not a valid type for this template |
| 31 | ^~~~~~~~ |
| 32 | ``` |
| 33 | |
| 34 | Rules for reading: |
| 35 | 1. Find the first error line (top of output) — that's your code |
| 36 | 2. Skip all the `note:` lines until you find "required from here" or "in instantiation of" |
| 37 | 3. The bottom of the stack shows the type that failed substitution |
| 38 | |
| 39 | ```bash |
| 40 | # Limit backtrace depth to reduce noise |
| 41 | g++ -ftemplate-backtrace-limit=3 prog.cpp |
| 42 | clang -ftemplate-depth=32 prog.cpp # default 1024 |
| 43 | |
| 44 | # Show simplified errors (GCC 12+) |
| 45 | g++ -fconcepts-diagnostics-depth=3 prog.cpp # for concept failures |
| 46 | ``` |
| 47 | |
| 48 | ### 2. SFINAE — legacy constraint technique |
| 49 | |
| 50 | SFINAE (Substitution Failure Is Not An Error) silently removes overloads that fail substitution: |
| 51 | |
| 52 | ```cpp |
| 53 | #include <type_traits> |
| 54 | |
| 55 | // Enable function only for arithmetic types |
| 56 | template <typename T, |
| 57 | std::enable_if_t<std::is_arithmetic_v<T>, int> = 0> |
| 58 | T square(T x) { return x * x; } |
| 59 | |
| 60 | // SFINAE with return type |
| 61 | template <typename T> |
| 62 | auto to_string(T x) -> std::enable_if_t<std::is_integral_v<T>, std::string> { |
| 63 | return std::to_string(x); |
| 64 | } |
| 65 | |
| 66 | // Void-t technique for detecting member existence |
| 67 | template <typename, typename = void> |
| 68 | struct has_size : std::false_type {}; |
| 69 | |
| 70 | template <typename T> |
| 71 | struct has_size<T, std::void_t<decltype(std::declval<T>().size())>> |
| 72 | : std::true_type {}; |
| 73 | ``` |
| 74 | |
| 75 | SFINAE errors are cryptic. Prefer concepts (C++20) for new code. |
| 76 | |
| 77 | ### 3. Concepts — modern constraints (C++20) |
| 78 | |
| 79 | ```cpp |
| 80 | #include <concepts> |
| 81 | |
| 82 | // Define a concept |
| 83 | template <typename T> |
| 84 | concept Arithmetic = std::is_arithmetic_v<T>; |
| 85 | |
| 86 | template <typename T> |
| 87 | concept Printable = requires(T x) { |
| 88 | { std::cout << x } -> std::same_as<std::ostream&>; |
| 89 | }; |
| 90 | |
| 91 | template <typename T> |
| 92 | concept Container = requires(T c) { |
| 93 | c.begin(); |
| 94 | c.end(); |
| 95 | c.size(); |
| 96 | typename T::value_type; |
| 97 | }; |
| 98 | |
| 99 | // Apply concept as constraint |
| 100 | template <Arithmetic T> |
| 101 | T square(T x) { return x * x; } |
| 102 | |
| 103 | // Abbreviated function template (C++20) |
| 104 | auto square(Arithmetic auto x) { return x * x; } |
| 105 | |
| 106 | // requires-clause (more complex conditions) |
| 107 | template <typename T> |
| 108 | requires Arithmetic<T> && (sizeof(T) >= 4) |
| 109 | T big_square(T x) { return x * x; } |
| 110 | |
| 111 | // Concept in auto parameter |
| 112 | void print_container(const Container auto& c) { |
| 113 | for (const auto& elem : c) std::cout << elem << ' '; |
| 114 | } |
| 115 | ``` |
| 116 | |
| 117 | ### 4. Requires expressions |
| 118 | |
| 119 | ```cpp |
| 120 | // requires { expression; } — checks expression is valid |
| 121 | // requires { expression -> type; } — checks type of expression |
| 122 | |
| 123 | template <typename T> |
| 124 | concept HasPush = requires(T c, typename T::value_type v) { |
| 125 | c.push_back(v); // must be valid |
| 126 | { c.front() } -> std::same_as<typename T::value_type&>; // type check |
| 127 | { c.size() } -> std::convertible_to<std::size_t>; // convertible |
| 128 | requires std::default_initializable<T>; // nested requirement |
| 129 | }; |
| 130 | |
| 131 | // Compound requires (all must hold) |
| 132 | template <typename T> |
| 133 | concept Sortable = requires(T a, T b) { |
| 134 | { a < b } -> std::convertible_to<bool>; |
| 135 | { a == b } -> std::convertible_to<bool>; |
| 136 | }; |
| 137 | ``` |
| 138 | |
| 139 | ### 5. SFINAE vs concepts comparison |
| 140 | |
| 141 | | Aspect | SFINAE | Concepts | |
| 142 | |--------|--------|---------| |
| 143 | | Syntax | Complex, verbose | Clean, readable | |
| 144 | | Error messages | Cryptic wall-of-text | Clear constraint failure | |
| 145 | | Compile time | Can be slow (many substitutions) | Generally faster | |
| 146 | | C++ version | C++11 | C++20 | |
| 147 | | Short-circuit | No | Yes (concept subsumption) | |
| 148 | | Use in `if constexpr` | Awkward | Natural | |
| 149 | | Overload ranking | Manually via priority | Automatic by constrai |