cpp-templates について
cpp-templates は、C++ テンプレートの問題を診断し改善する開発者向けのスキルです。テンプレート診断、SFINAE、C++20 の concepts、requires 式、Templight によるインスタンス化時間の分析を扱います。
主な機能
- 01インスタンス化履歴からテンプレートエラーを追跡
- 02concepts によるテンプレート制約
- 03SFINAE と concepts の比較
- 04requires 式の記述
- 05Templight によるインスタンス化時間の分析
- 06明示的インスタンス化によるコンパイル時間の短縮
活用シーン
- 01長いテンプレートコンパイルエラーの解読
- 02型に応じたテンプレートオーバーロードの制約
- 03SFINAE コードから concepts への移行
- 04インスタンス化にかかるコンパイル時間の調査
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ mohitmishra786/ low-level-dev-skills --skill cpp-templatesインストール後、Claude Code で/cpp-templatesを入力して読み込まれたか確認します。
詳細
C++ Templates
目的
エージェントがテンプレートのエラーメッセージを読み解いて修正する方法、より簡潔な制約としてコンセプトを使う方法、SFINAE とコンセプトのトレードオフ、そして Templight でテンプレートのインスタンス化の深さとコンパイル時間をプロファイリングする方法を案内します。
トリガー
- 「この膨大な C++ テンプレートエラーはどう読めばよいですか?」
- 「コンセプトを使ってテンプレートに制約を付けるにはどうすればよいですか?」
- 「SFINAE とコンセプトの違いは何ですか?」
- 「テンプレートのせいでコンパイルがとても遅くなります」
- 「requires 節はどう書けばよいですか?」
- 「テンプレートのインスタンス化にかかる時間をプロファイリングするにはどうすればよいですか?」
ワークフロー
1. テンプレートエラーメッセージを読む
テンプレートエラーには、インスタンス化の連鎖がすべて出力されます。下から上に読むのが基本です。
prog.cpp:25:5: error: no matching function for call to 'sort'
std::sort(v.begin(), v.end());
^~~~~~~~~
/usr/include/c++/13/bits/stl_algo.h:4869:5: note: candidate:
template<class _RAIter>
void std::sort(_RAIter, _RAIter)
note: template argument deduction/substitution failed:
prog.cpp:25:5: note: 'MyType' is not a valid type for this template
^~~~~~~~
読み解く際のルール:
- 最初のエラー行(出力の先頭)を見つけます。そこがあなたのコードです
- 「required from here」または「in instantiation of」が見つかるまで、すべての
note:行を読み飛ばします - スタックの一番下に、置換に失敗した型が表示されます
# Limit backtrace depth to reduce noise
g++ -ftemplate-backtrace-limit=3 prog.cpp
clang -ftemplate-depth=32 prog.cpp # default 1024
# Show simplified errors (GCC 12+)
g++ -fconcepts-diagnostics-depth=3 prog.cpp # for concept failures
2. SFINAE — 従来の制約手法
SFINAE(Substitution Failure Is Not An Error:置換失敗はエラーではない)は、置換に失敗したオーバーロードを暗黙に候補から除外します。
#include <type_traits>
// Enable function only for arithmetic types
template <typename T,
std::enable_if_t<std::is_arithmetic_v<T>, int> = 0>
T square(T x) { return x * x; }
// SFINAE with return type
template <typename T>
auto to_string(T x) -> std::enable_if_t<std::is_integral_v<T>, std::string> {
return std::to_string(x);
}
// Void-t technique for detecting member existence
template <typename, typename = void>
struct has_size : std::false_type {};
template <typename T>
struct has_size<T, std::void_t<decltype(std::declval<T>().size())>>
: std::true_type {};
SFINAE のエラーは難解です。新しいコードでは、コンセプト(C++20)を優先してください。
3. コンセプト — 現代的な制約(C++20)
#include <concepts>
// Define a concept
template <typename T>
concept Arithmetic = std::is_arithmetic_v<T>;
template <typename T>
concept Printable = requires(T x) {
{ std::cout << x } -> std::same_as<std::ostream&>;
};
template <typename T>
concept Container = requires(T c) {
c.begin();
c.end();
c.size();
typename T::value_type;
};
// Apply concept as constraint
template <Arithmetic T>
T square(T x) { return x * x; }
// Abbreviated function template (C++20)
auto square(Arithmetic auto x) { return x * x; }
// requires-clause (more complex conditions)
template <typename T>
requires Arithmetic<T> && (sizeof(T) >= 4)
T big_square(T x) { return x * x; }
// Concept in auto parameter
void print_container(const Container auto& c) {
for (const auto& elem : c) std::cout << elem << ' ';
}
4. requires 式
// requires { expression; } — checks expression is valid
// requires { expression -> type; } — checks type of expression
template <typename T>
concept HasPush = requires(T c, typename T::value_type v) {
c.push_back(v); // must be valid
{ c.front() } -> std::same_as<typename T::value_type&>; // type check
{ c.size() } -> std::convertible_to<std::size_t>; // convertible
requires std::default_initializable<T>; // nested requirement
};
// Compound requires (all must hold)
template <typename T>
concept Sortable = requires(T a, T b) {
{ a < b } -> std::convertible_to<bool>;
{ a == b } -> std::convertible_to<bool>;
};
5. SFINAE とコンセプトの比較
| 項目 | SFINAE | コンセプト |
|---|---|---|
| 構文 | 複雑で冗長 | 簡潔で読みやすい |
| エラーメッセージ | 難解で長大 | 制約の失敗が明確 |
| コンパイル時間 | 遅くなることがある(置換が多数発生) | 一般に高速 |
| C++ のバージョン | C++11 | C++20 |
| 短絡評価 | いいえ | はい(コンセプトの包含関係) |
if constexpr での使用 | 扱いにくい | 自然 |
| オーバーロードの順位付け | 優先順位を手動で指定 | 制約の具体性に基づいて自動決定 |
移行するには、enable_if をコンセプト制約に、void_t ヘルパーを requires に置き換えます。
6. Templight によるテンプレートのインスタンス化のプロファイリング
# Install Templight (Clang-based profiler)
# https://github.com/mikael-s-persson/templight
# Build with Templight tracing
clang++ -Xtemplight -profiler -Xtemplight -memory \
-std=c++17 prog.cpp -o prog
# Convert trace to visualizable format
templight-convert -f callgrind -o prof.out templight.pb
# View with KCachegrind
kcachegrind prof.out
# Find top template instantiation costs (without Templight)
# ClangBuildAnalyzer (easier)
ClangBuildAnalyzer --start /tmp/build
cmake --build build
ClangBuildAnalyzer --stop /tmp/build capture.bin
ClangBuildAnalyzer --analyze capture.bin | head -50
7. テンプレートのコンパイル時間を短縮する
// 1. Explicit instantiation — compile once, use everywhere
// header.h
template <typename T>
T transform(T x);
extern template int transform<int>(int); // suppress instantiation
// impl.cpp
#include "header.h"
template int transform<int>(int); // instantiate here only
// 2. Prefer function templates over class templates when possible
// (functions instantiate lazily; class templates instantiate eagerly)
// 3. Use concepts to short-circuit failed substitutions
// (concept check is faster than full substitution failure)
// 4. Split heavy template headers from lightweight ones
// - Put type definitions in forward_decls.h
// - Put template implementations in impl.h (include only where needed)
// 5. Use if constexpr instead of specialization
template <typename T>
void process(T x) {
if constexpr (std::is_integral_v<T>) {
handle_int(x);
} else {
handle_other(x);
}
}
関連スキル
- 全体的なコンパイル時間を短縮するための ccache と PCH については、
skills/build-systems/build-accelerationを使用します - Clang 固有の診断とコンセプトのエラー出力については、
skills/compilers/clangを使用します - もう 1 つの高度な C++20 機能については、
skills/low-level-programming/cpp-coroutinesを使用します
コメント
Markdown 対応