What: Enables/Disables template overloads based on type properties.
Example: Enable a function only for integer types.
#include
template<typename T>
typename std::enable_if<std::is_integral<T>::value, void>::type
process(T val) {
std::cout << "Processing integral: " << val << "\n";
}
template<typename T>
typename std::enable_if<!std::is_integral<T>::value, void>::type
process(T val) {
std::cout << "Processing non-integral: " << val << "\n";
}
int main() {
process(42); // Output: Processing integral: 42
process(3.14); // Output: Processing non-integral: 3.14
}
constexpr
and Compile-Time ComputationWhat: Evaluate expressions at compile time.
Example: Compile-time factorial.
constexpr int factorial(int n) {
return (n <= 1) ? 1 : n * factorial(n - 1);
}
int main() {
constexpr int result = factorial(5);
static_assert(result == 120, "Factorial of 5 must be 120");
std::cout << result; // Output: 120
}
What: Inspect/modify type properties (e.g., std::is_pointer
, std::remove_reference
).
Example: Check if a type is a pointer.
template<typename T>
struct is_pointer {
static constexpr bool value = false;
};
template<typename T>
struct is_pointer<T*> {
static constexpr bool value = true;
};
int main() {
std::cout << is_pointer<int*>::value; // Output: 1 (true)
std::cout << is_pointer<int>::value; // Output: 0 (false)
}
What: Process parameter packs recursively.
Example: Compile-time sum of a parameter pack.
template<typename T>
T sum(T val) { return val; }
template<typename T, typename... Args>
T sum(T first, Args... rest) {
return first + sum(rest...);
}
int main() {
std::cout << sum(1, 2, 3, 4); // Output: 10
}
What: Constrain template parameters with readable syntax.
Example: Ensure a type supports the +=
operator.
#include
template<typename T>
concept Addable = requires(T a, T b) {
{ a += b } -> std::same_as<T&>;
};
template<Addable T>
void add(T& a, const T& b) { a += b; }
int main() {
int x = 5;
add(x, 3);
std::cout << x; // Output: 8
}
Q1: Which code uses SFINAE correctly to enable a function for arithmetic types?
A)
template<typename T, typename = std::enable_if_t<std::is_arithmetic_v<T>>>
void func(T val) {}
B)
template<typename T>
void func(T val) requires std::is_arithmetic_v<T> {}
C) Both A and B
D) Neither
Q2: What is the output of sum(1, 2.5, 3)
if sum
uses auto
return type?
A) 6.5
B) 6
C) Compile error
D) Undefined
Q3: Which type trait removes const
from const int*
?
A) std::remove_const
B) std::remove_pointer
C) std::decay
D) std::remove_reference
Q4: What is the result of std::is_same_v
?
A) true
B) false
C) Compile error
D) Undefined
Q5: What does requires std::integral
enforce?
A) T
is an integer type
B) T
is an integral constant
C) T
supports arithmetic operations
D) T
is a template parameter
Q6: Which code checks if a type has a resize()
member function?
A)
template<typename T>
concept Resizable = requires(T t) { t.resize(0); };
B)
template<typename T>
struct has_resize {
template<typename U>
static auto test(U u) -> decltype(u.resize(0), void());
static bool value = std::is_invocable_v<test<T>, T>;
};
C) Both A and B
D) Neither
Q7: What is the output of:
constexpr int x = std::integral_constant<int, 5>::value;
std::cout << x;
A) 5
B) 0
C) Compile error
D) Undefined
Q8: Which code computes the factorial of 5 at compile time?
A)
constexpr int fact(int n) { return n <= 1 ? 1 : n * fact(n-1); }
int main() { constexpr int x = fact(5); }
B)
template<int N>
struct Factorial { static constexpr int value = N * Factorial<N-1>::value; };
template<> struct Factorial<0> { static constexpr int value = 1; };
int main() { constexpr int x = Factorial<5>::value; }
C) Both A and B
D) Neither
Q9: What does std::void_t
do?
A) Always resolves to void
B) Causes substitution failure if T
is invalid
C) Checks if T
is a valid type
D) All of the above
Q10: Which code uses fold expressions correctly?
A)
template<typename... Args>
auto sum(Args... args) { return (... + args); }
B)
template<typename... Args>
auto sum(Args... args) { return (args + ...); }
C) Both A and B
D) Neither
double
).std::remove_pointer::type
→ const int
).remove_cv_t
removes const
and volatile
).T
is integral).resize()
existence).integral_constant
stores 5).std::void_t
is used in SFINAE to check validity).Task: Create a trait is_container
that checks if a type has begin()
and end()
.
#include
template<typename T, typename = void>
struct is_container : std::false_type {};
template<typename T>
struct is_container<T, std::void_t<
decltype(std::declval<T>().begin()),
decltype(std::declval<T>().end())
>> : std::true_type {};
int main() {
std::cout << is_container<std::vector<int>>::value; // Output: 1
std::cout << is_container<int>::value; // Output: 0
}
Task: Use template metaprogramming to check if a number is prime.
template<int N, int D = N-1>
struct is_prime {
static constexpr bool value = (N % D != 0) && is_prime<N, D-1>::value;
};
template<int N>
struct is_prime<N, 1> { static constexpr bool value = true; };
template<int N>
struct is_prime<N, 0> { static constexpr bool value = false; };
int main() {
static_assert(!is_prime<4>::value, "4 is not prime");
static_assert(is_prime<5>::value, "5 is prime");
}
Task: Implement a Cloneable
base class using CRTP.
template<typename Derived>
class Cloneable {
public:
Derived* clone() const {
return new Derived(static_cast<const Derived&>(*this));
}
};
class Widget : public Cloneable<Widget> {
public:
Widget() = default;
Widget(const Widget&) { std::cout << "Widget cloned\n"; }
};
int main() {
Widget w;
auto* w2 = w.clone(); // Output: Widget cloned
}
std::hash
SupportTask: Use C++20 concepts to ensure a type is hashable.
#include
#include
template<typename T>
concept Hashable = requires(T t) {
{ std::hash<T>{}(t) } -> std::convertible_to<std::size_t>;
};
template<Hashable T>
void hash_check(T val) {
std::cout << std::hash<T>{}(val) << "\n";
}
int main() {
hash_check(42); // OK: int is hashable
// hash_check(std::vector()); // Error: vector is not hashable
}
Task: Implement a product
function using fold expressions.
template<typename... Args>
auto product(Args... args) {
return (... * args);
}
int main() {
std::cout << product(2, 3, 4); // Output: 24
std::cout << product(1.5, 2.0); // Output: 3
}
This guide covers SFINAE, constexpr
, type traits, variadic templates, and C++20 concepts. The exercises validate advanced template metaprogramming techniques, including compile-time computations, CRTP, and custom type traits. Test cases ensure correctness for edge cases like prime checks and hashability.