Appearance
Templates, Concepts, and Generic Programming
Templates generate declarations and definitions from types or compile-time values. Unlike Java or C# generics, they can operate over non-type values, participate in overload resolution, and instantiate specialized machine code.
A template expresses required operations
cpp
#include <iostream>
#include <vector>
template <typename Range>
auto sum(const Range& values) {
typename Range::value_type total{};
for (const auto& value : values) total += value;
return total;
}
int main() {
std::cout << sum(std::vector<int>{3, 5, 8}) << '\n';
}The compiler deduces Range, instantiates a specialization, and checks operations in that context. Unconstrained templates can produce long diagnostics far from the call when requirements are unmet.
Concepts name constraints
C++20 concepts let an interface state requirements:
cpp
#include <concepts>
template <std::integral T>
T greatest_common_divisor(T left, T right);Constraints participate in overload selection and improve diagnostics. A concept should describe a meaningful semantic category, not merely bundle arbitrary syntax checks.
requires expressions can test valid operations, nested types, and relationships. They do not execute runtime validation.
Overload resolution chooses before execution
At a call site, the compiler collects candidate functions, removes candidates that are not viable, ranks the conversions required by the remaining candidates, and selects one best match. Templates add deduction and constraints to that process.
cpp
void write(int value);
void write(double value);
write(3); // exact int match
write(3.5); // exact double matchImplicit conversions, default arguments, forwarding references, and unconstrained templates can make two candidates equally good or select an overload the author did not expect. Keep overload sets cohesive: every overload should represent the same conceptual operation. Use explicit constructors and constraints to prevent unrelated conversions from entering the set.
Argument-dependent lookup (ADL) adds functions from namespaces and classes associated with the argument types. It is why an unqualified call can find a customization beside a type:
cpp
using std::swap;
swap(left, right); // permits an ADL-found swap for the argument typeADL is useful for established customization patterns but can make distant functions candidates. Avoid placing unrelated overloads in a type's namespace, and qualify a call when customization is not intended.
Instantiation affects source organization
The compiler generally needs a template definition where it instantiates a specialization, so definitions commonly live in headers. Alternatives include explicit instantiation for a controlled set of types and C++20 modules where toolchain support fits the project.
Every translation unit may instantiate equivalent code; linkers commonly merge it, but compile time and diagnostics remain. Limit heavy transitive headers and measure template expansion.
Type traits expose compile-time properties
The <type_traits> library supplies compile-time predicates and transformations:
cpp
#include <type_traits>
template <typename T>
constexpr bool byte_serializable =
std::is_trivially_copyable_v<T> && std::has_unique_object_representations_v<T>;A trait reports a language property; it does not prove a complete domain contract. Even a trivially copyable type can contain padding, use host byte order, or have a representation unsuitable for a stable file format.
Traits power conditional implementation and older SFINAE-based APIs. In C++20 interfaces, concepts usually communicate requirements more directly. Traits remain valuable inside implementations and for properties not naturally expressed as behavioral concepts.
Class template argument deduction is contextual
Since C++17, constructors can let the compiler deduce class template arguments:
cpp
std::pair entry{42, std::string{"ready"}};The compiler uses implicit or user-provided deduction guides. Deduction creates a concrete specialization; it does not make the object dynamically generic. Add a custom guide only when constructor parameters determine template arguments unambiguously. Explicit template arguments are clearer when policy types or ownership would otherwise be surprising.
Variadic templates replace unsafe ellipses for typed code
Parameter packs support type-safe forwarding and heterogeneous construction. Perfect forwarding uses forwarding references and std::forward, but should appear inside abstractions that truly preserve caller value categories—not as decorative complexity.
Fold expressions reduce a pack with an operator:
cpp
template <typename... Values>
auto add(Values... values) {
return (values + ...);
}The operation must still have sensible types, ordering, and overflow behavior.
Specialization and overloads serve different designs
Function overloading is often clearer than function-template specialization. Class-template partial specialization can adapt representation for categories of types. Prefer concepts and ordinary overload resolution before intricate SFINAE machinery in new C++20 code, while learning to recognize enable_if and detection idioms in older projects.
Optional prompts
Explain: Why are template definitions usually in headers?
Answer: A translation unit instantiating a template generally needs to see its definition. A declaration alone is insufficient unless required specializations are explicitly instantiated elsewhere.
Explain: What does a concept add beyond documentation?
Answer: It is a compile-time constraint used in viability and overload ordering, so invalid uses fail closer to the interface with a named requirement.
Explain: Why can swap(left, right) intentionally remain unqualified?
Answer: Bringing
std::swapinto scope provides the fallback, while argument-dependent lookup can find a more appropriate overload declared beside the argument type.