Templates must be fully defined in every translation unit where they are used. This is enforced by the inclusion model (defining templates in headers).
// vector.hpp
template<typename T>
class Vector {
T* data;
public:
Vector(); // Declaration
void push_back(const T& val); // Declaration
};
// Definitions must be in the header!
template<typename T>
Vector<T>::Vector() : data(nullptr) {}
template<typename T>
void Vector<T>::push_back(const T& val) { /* ... */ }
If the definitions are not visible when the template is instantiated (e.g., in main.cpp
), the linker will fail.
Linker errors occur when the compiler cannot find the template definition. Use explicit instantiation to force instantiation in a specific file.
// vector.cpp
#include "vector.hpp"
// Explicitly instantiate for int
template class Vector<int>;
// main.cpp
#include "vector.hpp"
int main() {
Vector<int> v; // Works because of explicit instantiation
v.push_back(42);
}
Template errors can be verbose. Use static_assert
and C++20 concepts to improve diagnostics.
template<typename T>
void print(const T& val) {
static_assert(
requires { std::cout << val; },
"T must support operator<<"
);
std::cout << val;
}
int main() {
print(10); // OK
// print(std::vector{}); // Fails static_assert
}
static_assert
provides a clear error message if T
doesn’t support operator<<
.
Templates can be instantiated multiple times across translation units, but all instantiations must be identical.
// utils.hpp
template<typename T>
T add(T a, T b) { return a + b; } // Definition in header
// main.cpp
#include "utils.hpp"
int main() { add(1, 2); } // OK: Same as other units
Violating ODR by modifying template behavior in different files leads to undefined behavior.
Precompiled headers cache frequently used headers (e.g., STL, template-heavy code) to speed up compilation.
# Compile precompiled header
g++ -std=c++20 -xc++-header vector.hpp -o vector.hpp.gch
# Use precompiled header
g++ -std=c++20 -include vector.hpp main.cpp
Concepts replace SFINAE with readable constraints.
template<typename T>
concept Addable = requires(T a, T b) { a + b; };
template<Addable T>
T sum(T a, T b) { return a + b; }
int main() {
sum(3, 4); // OK
// sum(std::vector{}, std::vector{}); // Error
}
Concepts enforce type requirements at compile time.
.hpp
) and definitions (.tpp
)..tpp
at the end of the .hpp
.// vector.hpp
template<typename T>
class Vector { /* ... */ };
#include "vector.tpp" // Include definitions
// vector.tpp
template<typename T>
void Vector<T>::push_back(const T& val) { /* ... */ }
Templates can cause code bloat (multiple instantiations). Mitigate with:
extern template
to suppress implicit instantiation.// Explicitly instantiate in vector.cpp
extern template class Vector<int>; // Suppress implicit instantiation
void test_vector() {
Vector<int> v;
v.push_back(10);
assert(v.size() == 1);
}
int main() {
test_vector();
}
noexcept
, constexpr
).typename
/template
to resolve dependent names.noexcept
.template<typename T>
class Wrapper {
public:
~Wrapper() noexcept { /* Critical for containers */ }
};
Q1: What causes a linker error when using templates?
A) Missing template declaration
B) Missing template definition in the translation unit
C) Missing inline
keyword
D) Missing typename
keyword
Q2: Which keyword suppresses implicit template instantiation?
A) extern
B) export
C) static
D) template
Q3: What is the purpose of static_assert
in template code?
A) To validate runtime conditions
B) To enforce compile-time constraints
C) To improve runtime performance
D) To suppress compiler warnings
Q4: Which C++20 feature replaces SFINAE for type constraints?
A) constexpr
B) noexcept
C) Concepts
D) Modules
Q5: How does the One-Definition Rule (ODR) apply to templates?
A) Templates must be defined once per program
B) Template specializations can differ across translation units
C) Template definitions must be identical in all translation units
D) Template declarations must be in headers
Q6: What happens if a template definition is split into .hpp
and .cpp
files?
A) Compilation error
B) Linker error
C) Runtime error
D) No error
Q7: Which code violates the ODR?
// file1.cpp
template<typename T> void foo(T) {}
// file2.cpp
template<typename T> void foo(T) {}
A) Yes
B) No
Q8: What does extern template class Vector
do?
A) Forces implicit instantiation
B) Suppresses implicit instantiation
C) Declares a template specialization
D) Defines a template
Q9: Which feature reduces redundant template compilation overhead?
A) Precompiled headers
B) constexpr
C) noexcept
D) auto
Q10: Why does std::cout << typeid(T).name()
fail for template types?
A) It returns a mangled name
B) It is not constexpr
C) typeid
is not supported for templates
D) It requires RTTI
extern template
suppresses implicit instantiation).static_assert
enforces compile-time constraints).extern template
suppresses implicit instantiation).typeid(T).name()
returns a compiler-specific mangled name).Problem:
// stack.hpp
template<typename T>
class Stack {
public:
void push(T val); // Declaration only
};
// main.cpp
#include "stack.hpp"
int main() {
Stack<int> s;
s.push(42); // Linker error
}
Solution:
// stack.hpp
template<typename T>
class Stack {
public:
void push(T val) { /* Definition */ }
};
Test Case:
int main() {
Stack<int> s;
s.push(5);
assert(s.top() == 5);
}
Task: Create a function sum
that only accepts arithmetic types.
Solution:
template<typename T>
requires std::is_arithmetic_v<T>
T sum(T a, T b) { return a + b; }
Test Case:
int main() {
std::cout << sum(3, 4); // OK
// sum("a", "b"); // Fails constraint
}
Task: Explicitly instantiate Vector
to avoid redundant compilation.
Solution:
// vector.hpp
template<typename T> class Vector { /* ... */ };
// vector.cpp
#include "vector.hpp"
template class Vector<int>; // Explicit instantiation
Test Case:
// main.cpp
#include "vector.hpp"
int main() {
Vector<int> v; // Uses explicit instantiation
}
Task: Precompile vector.hpp
to speed up builds.
Implementation:
g++ -std=c++20 -xc++-header vector.hpp -o vector.hpp.gch
g++ -std=c++20 -include vector.hpp main.cpp
Test: Measure compile time before/after precompilation.
static_assert
Problem:
template<typename T>
void serialize(T val) { /* Assumes T has serialize() */ }
Solution:
template<typename T>
void serialize(T val) {
static_assert(requires { val.serialize(); }, "T must have serialize()");
}
Test Case:
struct Data { void serialize() {} };
int main() {
serialize(Data{}); // OK
// serialize(10); // Fails static_assert
}
This guide reinforces critical template concepts:
extern template
to control compilation.Test cases validate solutions and ensure code correctness.