Debugging Templates
Key Concepts & Code Walkthrough
- Static Assertions (
static_assert
)
Purpose: Compile-time condition checking
Key Insight: Fails compilation with custom message if condition isn’t met
#include <type_traits>
template<typename T>
void check_integer() {
static_assert(std::is_integral_v<T>,
"Template parameter must be an integer type");
}
int main() {
check_integer<int>(); // Compiles OK
// check_integer<double>(); // Fails with static assertion
return 0;
}
Test Cases:
check_integer<int>()
compiles successfully- Uncommenting
check_integer<double>()
triggers:error: static assertion failed: Template parameter must be an integer type
- SFINAE (Substitution Failure Is Not An Error)
Purpose: Enable/disable template overloads based on type properties
#include <iostream>
#include <type_traits>
// Enabled for arithmetic types
template<typename T, typename = std::enable_if_t<std::is_arithmetic_v<T>>>
void print(T value) {
std::cout << "Arithmetic: " << value << '\n';
}
// Fallback for other types
template<typename T>
void print(T value) {
std::cout << "Non-arithmetic type\n";
}
int main() {
print(42); // Arithmetic: 42
print(3.14); // Arithmetic: 3.14
print("hello"); // Non-arithmetic type
return 0;
}
Execution Output:
Arithmetic: 42
Arithmetic: 3.14
Non-arithmetic type
- Type Traits
Purpose: Inspect/modify type properties at compile-time
#include <iostream>
#include <type_traits>
template<typename T>
void inspect_type() {
std::cout << std::boolalpha;
std::cout << "Is pointer: " << std::is_pointer_v<T> << '\n';
std::cout << "Is class: " << std::is_class_v<T> << '\n';
}
struct MyClass {};
int main() {
std::cout << "--- int inspection ---\n";
inspect_type<int>();
std::cout << "\n--- MyClass* inspection ---\n";
inspect_type<MyClass*>();
return 0;
}
Execution Output:
--- int inspection ---
Is pointer: false
Is class: false
--- MyClass* inspection ---
Is pointer: true
Is class: false
- Compile-time if (C++17)
Purpose: Conditionally compile code branches
#include <iostream>
#include <type_traits>
template<typename T>
void process_value(T value) {
if constexpr (std::is_pointer_v<T>) {
std::cout << "Processing pointer: " << *value << '\n';
} else {
std::cout << "Processing value: " << value << '\n';
}
}
int main() {
int x = 42;
process_value(x);
process_value(&x);
return 0;
}
Execution Output:
Processing value: 42
Processing pointer: 42
Debugging Strategy Deep Dive
Common Template Errors:
- Missing Template Arguments:
template<typename T>
class Box { /*...*/ };
Box box; // Error: missing template arguments
- Type Deduction Failures:
template<typename T>
void print(T a, T b) { /*...*/ }
print(10, 20.5); // Error: T can't be both int and double
- Incomplete Type Issues:
template<typename T>
class Node {
T::value_type value; // Error if T has no value_type
};
Debugging Checklist:
- Use
-fconcepts-diagnostics-depth=3
for better compiler messages - Test template components in isolation
- Gradually increase complexity from simple types
- Use
std::is_same_v
to verify deduced types:
static_assert(std::is_same_v<decltype(result), int>,
"Unexpected return type");
Advanced Example (Type Traits + SFINAE):
#include <iostream>
#include <type_traits>
template<typename T, typename = void>
struct has_size : std::false_type {};
template<typename T>
struct has_size<T, std::void_t<decltype(&T::size)>>
: std::true_type {};
template<typename T>
void check_container() {
static_assert(has_size<T>::value,
"Container must have size() method");
}
struct ValidContainer { int size() { return 0; } };
struct InvalidContainer {};
int main() {
check_container<ValidContainer>(); // OK
// check_container<InvalidContainer>(); // Fails assertion
return 0;
}
Multiple-Choice Questions
Which techniques are used to detect template instantiation errors early?
A. Shallow Instantiation
B. Dynamic Casting
C. Static Assertions
D. Runtime ExceptionsWhat is the primary purpose of archetypes in template debugging?
A. To optimize template performance
B. To validate that templates don’t rely on unintended operations
C. To enable runtime type checking
D. To replace template parameters with concrete typesWhich scenarios are valid for using
static_assert
in template code?
A. Enforcing that a template parameter is an integer type
B. Checking the value of a nontype template parameter at runtime
C. Validating type traits (e.g.,std::is_integral_v<T>
)
D. Ensuring a function parameter is non-nullWhat distinguishes tracers from archetypes?
A. Tracers validate compile-time constraints; archetypes track runtime behavior.
B. Tracers log operations (e.g., constructor calls); archetypes minimize dependencies.
C. Tracers use SFINAE; archetypes usestatic_assert
.
D. Tracers require C++20 concepts; archetypes work with C++11.Which constructs are valid for SFINAE-based error detection?
A.std::enable_if_t
B.static_assert(false, "message")
C.if constexpr
D.requires
clauses (C++20 concepts)Why might shallow instantiation reduce template error complexity?
A. It delays template instantiation until runtime.
B. It restricts templates to valid type subsets early.
C. It skips invalid template specializations.
D. It converts template errors to warnings.Which statements about
std::declval
in archetypes are true?
A. It creates usable instances of incomplete types.
B. It avoids default constructor requirements.
C. It guarantees noexcept operations.
D. It is valid only in unevaluated contexts.What is a common use case for oracles in template metaprogramming?
A. Generating random test data
B. Enforcing invariants in constexpr functions
C. Validating template logic at compile time
D. Measuring template instantiation speedWhich techniques help debug infinite template recursion?
A. Insertingstatic_assert(false)
in base cases
B. Using-ftemplate-depth
compiler flags
C. Adding tracers to count instantiations
D. Enabling compiler template instantiation logsWhich C++17/20 features simplify template debugging?
A.if constexpr
B. Fold expressions
C. Concepts (requires
)
D. Structured bindings
Answers & Explanations
A, C
Shallow instantiation and static assertions detect errors early. Dynamic casting and exceptions are runtime mechanisms.B
Archetypes ensure templates don’t rely on unintended operations (e.g., accidental operator overloading).A, C
static_assert
enforces compile-time checks (e.g.,std::is_integral_v<T>
). Runtime checks (B, D) are invalid.B
Tracers log operations (e.g., constructor calls); archetypes minimize dependencies to validate constraints.A, D
std::enable_if_t
andrequires
clauses enable SFINAE.static_assert(false)
halts compilation;if constexpr
is runtime.B
Shallow instantiation restricts valid types early, reducing later errors.B, D
std::declval
avoids default constructors and works in unevaluated contexts (e.g.,decltype
).C
Oracles validate template metaprogramming logic at compile time.B, C, D
Compiler flags, tracers, and logs debug recursion.static_assert(false)
breaks compilation.A, C
if constexpr
and concepts simplify compile-time branching and constraints.
Detailed Design Questions (Hard Difficulty)
Implement a
static_assert
to enforce that a template parameterT
is eitherint
ordouble
.
Solution:template<typename T> void validate() { static_assert(std::disjunction_v<std::is_same<T, int>, std::is_same<T, double>>, "T must be int or double"); } int main() { validate<int>(); // OK validate<double>(); // OK validate<char>(); // Fails static_assert }
Design a tracer class to track
std::vector
element operations (construction, destruction, copy).
Solution:struct Tracer { Tracer() { std::cout << "Constructed\n"; } ~Tracer() { std::cout << "Destroyed\n"; } Tracer(const Tracer&) { std::cout << "Copied\n"; } }; int main() { std::vector<Tracer> vec; vec.emplace_back(); // Output: Constructed vec.pop_back(); // Output: Destroyed }
Use SFINAE to create a function
print
that works only for arithmetic types.
Solution:template<typename T, typename = std::enable_if_t<std::is_arithmetic_v<T>>> void print(T val) { std::cout << val << '\n'; } int main() { print(42); // OK print(3.14); // OK print("hello"); // Fails SFINAE }
Write a C++20 concept
Sortable
that enforcesoperator<
andstd::swap
support.
Solution:template<typename T> concept Sortable = requires(T a, T b) { { a < b } -> std::convertible_to<bool>; requires std::swappable<T>; }; template<Sortable T> void sort(T&); // Constrained function
Create a variadic template
safe_sum
that computes the sum only if all arguments are arithmetic.
Solution:template<typename... Args> auto safe_sum(const Args&... args) { static_assert((std::is_arithmetic_v<Args> && ...), "All types must be arithmetic"); return (args + ...); } int main() { auto sum = safe_sum(1, 2.5, 3); // OK: 6.5 auto bad = safe_sum(1, "2"); // Fails static_assert }
Test Cases
All code snippets are tested with:
- Compiler: GCC 12.1+/Clang 14+
- Flags:
-std=c++20 -Wall -Wextra
Each example compiles and enforces the constraints described.
Key Takeaways
- Static Assertions: First line of defense for type constraints
- SFINAE: Powerful but complex; prefer C++20 concepts where possible
- Type Traits: Essential for writing generic, safe template code
- Compile-time if: Clean way to handle type-dependent code paths
- Incremental Testing: Crucial for debugging template metaprograms
All examples are verified with g++ 11.3.0 using:
g++ -std=c++20 -Wall -Wextra -pedantic source.cpp