Basic Template Terminology
Key Concepts & Code Walkthrough
- Class Template vs. Template Class
- Class Template: Blueprint for generating classes. Not a real class.
- Template Class: Actual class generated by instantiating a class template.
#include <iostream>
// CLASS TEMPLATE
template<typename T>
class Box {
T content;
public:
Box(T c) : content(c) {}
T get() const { return content; }
};
int main() {
Box<int> intBox(42); // Template class Box<int>
Box<std::string> strBox("Hello"); // Template class Box<std::string>
std::cout << intBox.get() << "\n"; // 42
std::cout << strBox.get() << "\n"; // Hello
}
- Substitution, Instantiation, Specialization
- Substitution: Replacing template parameters with concrete types during compilation.
- Instantiation: Generating actual code from a template.
- Explicit Specialization: Custom implementation for specific types.
// Primary template
template<typename T>
void printType() { std::cout << "Generic\n"; }
// Explicit specialization for int
template<>
void printType<int>() { std::cout << "int\n"; }
int main() {
printType<double>(); // Instantiates primary template: "Generic"
printType<int>(); // Uses specialization: "int"
}
- Declaration vs. Definition
- Templates must be defined in headers (ODR rules).
// mytemplate.h
template<typename T>
T add(T a, T b) { return a + b; } // Declaration & definition together
// main.cpp
#include <iostream>
#include "mytemplate.h"
int main() {
std::cout << add(3, 4) << "\n"; // 7 (int version instantiated)
std::cout << add(2.5, 3.5) << "\n";// 6.0 (double version)
}
- One-Definition Rule (ODR)
- A template can appear in multiple translation units but must be identical.
// header.h
template<typename T>
T max(T a, T b) { return (a > b) ? a : b; }
// unit1.cpp
#include "header.h"
void foo() { max(10, 20); }
// unit2.cpp
#include "header.h"
void bar() { max(3.14, 2.71); }
// Linker sees two instantiations: max<int> and max<double> (no conflict)
- Template Parameters vs. Arguments
- Parameters: Placeholders in template definitions (
T
intemplate<typename T>
). - Arguments: Concrete types/values used during instantiation (
int
inBox<int>
).
template<typename T, int N> // T and N are parameters
class Array {
T data[N];
public:
T& operator[](int idx) { return data[idx]; }
};
int main() {
Array<double, 5> arr; // double and 5 are arguments
arr[0] = 3.14;
std::cout << arr[0] << "\n"; // 3.14
}
- SFINAE (Substitution Failure Is Not An Error)
- Invalid substitutions are silently removed from overload resolution.
#include <iostream>
// Version 1: Enabled if T has a nested type 'type'
template<typename T, typename = typename T::type>
void test(int) { std::cout << "Has nested type\n"; }
// Version 2: Catch-all
template<typename T>
void test(...) { std::cout << "No nested type\n"; }
struct Foo { using type = int; };
int main() {
test<Foo>(0); // Calls version 1
test<int>(0); // Calls version 2 (SFINAE discards version 1)
}
Key Takeaways
- Class Template is a blueprint; Template Class is the instantiated result.
- Instantiation happens implicitly when you use a template with concrete types.
- Always define templates in headers to satisfy the ODR.
- SFINAE allows graceful handling of invalid substitutions in overload resolution.
Each code example includes a main()
function for immediate testing. Compile with:
g++ -std=c++17 example.cpp -o example && ./example
Multiple-Choice Questions (Hard Difficulty)
Which of the following statements about template argument deduction are correct?
- A. Template argument deduction considers implicit conversions.
- B. Deduction fails if the template parameter is a non-deduced context.
- C.
decltype(auto)
as a return type always deduces to a reference. - D. SFINAE can prevent substitution failures from causing compilation errors.
In the context of two-phase name lookup for templates:
- A. Dependent names are resolved during the first phase.
- B. Non-dependent names are resolved at template definition.
- C. ADL (Argument-Dependent Lookup) occurs in the second phase.
- D.
typename
is required for dependent types in the first phase.
Which scenarios violate the One-Definition Rule (ODR) for templates?
- A. Two explicit specializations of
std::vector<bool>
in different translation units. - B. Identical implicit instantiations of a class template across translation units.
- C. A function template defined in a header included in multiple files.
- D. A partial specialization declared after its primary template.
- A. Two explicit specializations of
Select valid uses of template template parameters:
- A.
template<template<typename> class Container> class MyClass { ... };
- B.
template<typename T, template<T> class U> void f() { ... }
- C.
template<template<auto> class Container> struct X { ... };
- D.
template<template<typename...> class C> void g(C<int>) { ... }
- A.
Which statements about variable templates are true?
- A. They must be declared with
constexpr
. - B. They can be specialized like class templates.
- C.
template<typename T> T pi = T(3.1415926535);
is valid. - D. They cannot have non-type template parameters.
- A. They must be declared with
Regarding SFINAE (Substitution Failure Is Not An Error):
- A. It applies to errors in the immediate context of substitution.
- B.
std::enable_if_t
leverages SFINAE to enable/disable overloads. - C. A failed
static_assert
inside a template triggers SFINAE. - D. It works with partial specializations of class templates.
Which of the following are valid fold expressions (C++17)?
- A.
(args + ...)
- B.
(0 + ... + args)
- C.
(std::cout << ... << args)
- D.
(... && args)
- A.
Identify correct behaviors of class template argument deduction (CTAD):
- A. Deduction guides override constructor-based deduction.
- B.
std::array{1, 2, 3}
deduces tostd::array<int, 3>
. - C. Explicit template arguments disable CTAD.
- D. Aggregate classes cannot use CTAD without guides.
About
auto
and template type deduction:- A.
auto x{1, 2};
deducesx
asstd::initializer_list<int>
. - B.
auto&&
follows universal reference rules. - C.
decltype(auto)
preserves value categories. - D.
auto
in lambda parameters requires C++20.
- A.
Which techniques ensure compile-time polymorphism?
- A. CRTP (Curiously Recurring Template Pattern)
- B. Virtual functions
- C. Tag dispatch
- D.
if constexpr
branches
Design Questions (Hard Difficulty)
Design a
constexpr
-enabledTuple
class template supporting:- Variable number of elements of heterogeneous types.
- Compile-time element access via
get<Index>(tuple)
. - Structured binding support.
Provide test cases for construction, access, and structured bindings.
Implement a
TypeList
metaprogramming utility with:- Operations:
Length
,Get
,Concat
,Filter
(remove types matching a trait). - SFINAE-based trait to check if a type is in the
TypeList
.
Test withTypeList<int, float, char>
filtering for arithmetic types.
- Operations:
Create a
ThreadSafeQueue
class template using:- A lock-based design with
std::mutex
andstd::condition_variable
. - Perfect forwarding for
emplace()
. - Timeout support for
try_pop()
.
Test with producer-consumer threads and verify exception safety.
- A lock-based design with
Design a
Variant
class mimickingstd::variant
with:- Type-safe storage for alternative types.
visit()
using a generic lambda andoverload
pattern.- Compile-time checks for duplicate types.
Test withVariant<int, float, std::string>
and visitation.
Implement a
Serializer
using SFINAE to handle:- Primitive types (direct serialization).
- Containers with
begin()
/end()
. - Custom serialization via
serialize()
member function.
Test withstd::vector
, a customstruct
, and astd::map
.
Answers and Explanations
Multiple-Choice Answers
B, D
- B: Non-deduced contexts (e.g., nested types) prevent deduction.
- D: SFINAE discards invalid substitutions without errors.
- A: Deduction ignores implicit conversions.
- C:
decltype(auto)
deduces references for lvalues, values otherwise.
B, C
- B: Non-dependent names are resolved at definition.
- C: ADL occurs during the second phase.
- A: Dependent names are resolved at instantiation.
- D:
typename
is needed in the second phase.
A
- A: Multiple explicit specializations violate ODR.
- B/C: Implicit instantiations and inline definitions are allowed.
- D: Partial specializations must follow primary templates.
A, C, D
- A/C/D: Valid syntax for template template parameters.
- B: Non-type template parameters cannot be dependent on type
T
.
B, C
- B: Variable templates can be specialized.
- C: Valid variable template.
- A:
constexpr
is optional. - D: Non-type parameters are allowed.
A, B
- A/B: SFINAE applies to immediate context and
enable_if
. - C:
static_assert
causes hard errors. - D: SFINAE doesn’t apply to class partial specializations.
- A/B: SFINAE applies to immediate context and
A, D
- A/D: Valid unary/binary fold expressions.
- B: Invalid syntax (parentheses needed).
- C: Binary folds require consistent operators.
A, C
- A/C: Guides override, explicit args disable CTAD.
- B:
std::array
deduction requiresstd::to_array
. - D: Aggregates can use CTAD with C++17.
B, C, D
- B/D: Correct universal ref and C++20 lambda syntax.
- C:
decltype(auto)
preserves references. - A:
auto x{1,2};
is invalid (C++17: single-element list).
A, C, D
- A/C/D: Compile-time techniques.
- B: Runtime polymorphism.
Design Answers
Tuple Implementation
template<typename... Ts> class Tuple {}; template<typename T, typename... Ts> class Tuple<T, Ts...> : Tuple<Ts...> { T value; public: Tuple(T t, Ts... ts) : Tuple<Ts...>(ts...), value(t) {} template<size_t I> auto& get() { /*...*/ } }; // Test: Tuple<int, float> t(42, 3.14f); auto& x = t.get<0>(); // 42
TypeList Metaprogramming
template<typename... Ts> struct TypeList {}; template<typename List> struct Length; template<template<class...> class List, typename... Ts> struct Length<List<Ts...>> : std::integral_constant<size_t, sizeof...(Ts)> {}; // Test: using TL = TypeList<int, float, char>; static_assert(Length<TL>::value == 3);
ThreadSafeQueue
template<typename T> class ThreadSafeQueue { std::queue<T> queue; std::mutex mtx; std::condition_variable cv; public: template<typename U> void emplace(U&& u) { std::lock_guard lock(mtx); queue.emplace(std::forward<U>(u)); cv.notify_one(); } }; // Test with threads...
Variant Class
template<typename... Ts> class Variant { std::aligned_union_t<0, Ts...> storage; size_t index; public: template<typename T> requires (std::is_constructible_v<T>) Variant(T&& t) { /*...*/ } }; // Test visitation...
Serializer with SFINAE
template<typename T, typename = void> struct Serializer { static void serialize(const T& t) { /*...*/ } }; // Test with custom types...