Ready to practice?
Sign up to access interactive coding exercises and track your progress.
Template Instantiation
Understand how the compiler generates concrete functions from templates.
Calling function templates
In the previous lesson, we created a function template:
template <typename T>
T square(T value)
{
return value * value;
}
A template alone does nothing. It's a blueprint waiting to be used. This lesson covers how to turn that blueprint into actual functions.
Explicit template arguments
To call a function template, provide the type in angle brackets:
square<int>(5); // Generate and call square for int
square<double>(3.14); // Generate and call square for double
Complete example:
#include <iostream>
template <typename T>
T square(T value)
{
return value * value;
}
int main()
{
std::cout << square<int>(7) << '\n'; // 49
std::cout << square<double>(2.5) << '\n'; // 6.25
return 0;
}
Output:
49
6.25
When the compiler sees square<int>(7), it checks if a function square(int) exists. Finding none, it uses the template to create one by substituting int for every T.
The instantiation process
Template instantiation is the compiler's process of generating a concrete function from a template. The result is called a function instance or specialization.
The compiler performs a textual substitution:
// Template
template <typename T>
T square(T value)
{
return value * value;
}
// Generated for square<int>
int square(int value)
{
return value * value;
}
// Generated for square<double>
double square(double value)
{
return value * value;
}
Each unique type produces one instantiation. Calling square<int>(7) and square<int>(12) both use the same generated function.
Type deduction
Writing the type explicitly is often redundant:
square<int>(7); // We're passing an int, asking for int
The compiler can figure this out automatically through template argument deduction:
square(7); // Compiler deduces T = int from the argument
square(2.5); // Compiler deduces T = double from the argument
Both produce the same instantiations as the explicit versions.
Full example with deduction:
#include <iostream>
template <typename T>
T square(T value)
{
return value * value;
}
int main()
{
std::cout << square(8) << '\n'; // Deduces int, prints 64
std::cout << square(1.5) << '\n'; // Deduces double, prints 2.25
std::cout << square(3.0f) << '\n'; // Deduces float, prints 9
return 0;
}
Empty angle brackets
There's a third syntax using empty brackets:
square<int>(7); // Explicit type
square<>(7); // Deduce type, only consider templates
square(7); // Deduce type, consider all functions
The difference between square<>() and square() matters when both a template and a regular function could match:
#include <iostream>
template <typename T>
T square(T value)
{
std::cout << "template\n";
return value * value;
}
int square(int value)
{
std::cout << "regular function\n";
return value * value;
}
int main()
{
square<int>(5); // Calls template version
square<>(5); // Calls template version (regular functions excluded)
square(5); // Calls regular function (preferred over template)
return 0;
}
When a regular function and a template instantiation match equally well, the regular function wins. This allows specialized implementations to override generic templates.
When deduction fails
Deduction requires the compiler to determine a single consistent type for each template parameter. Conflicting information causes failure:
template <typename T>
T larger(T a, T b)
{
return (a > b) ? a : b;
}
int main()
{
larger(10, 20); // OK: both int, T = int
larger(1.5, 2.5); // OK: both double, T = double
larger(10, 2.5); // Error: int vs double, T is ambiguous
return 0;
}
The call larger(10, 2.5) fails because the first argument suggests T = int while the second suggests T = double. The compiler won't guess which you intended.
Solutions:
larger<double>(10, 2.5); // Explicit: use double, convert 10 to 10.0
larger(10.0, 2.5); // Make both arguments double
Mixed template and regular parameters
Templates can combine placeholder types with fixed types:
template <typename T>
T scale(T value, double factor)
{
return static_cast<T>(value * factor);
}
int main()
{
scale(100, 0.5); // T = int, result is 50
scale(3.14, 2.0); // T = double, result is 6.28
scale(10, 1.5); // T = int, result is 15
return 0;
}
The first parameter determines T. The second parameter is always double.
Templates that fail to compile
A template compiles only if the generated code is valid. Consider:
template <typename T>
T square(T value)
{
return value * value;
}
This works for numeric types but fails for types that don't support multiplication:
#include <string>
int main()
{
std::string text{ "hello" };
square(text); // Error: can't multiply strings
return 0;
}
The compiler attempts to generate:
std::string square(std::string value)
{
return value * value; // Error: no operator* for string
}
Since std::string doesn't support *, compilation fails. This is expected behavior. Templates work with types that support the operations used in the template body.
Syntactically valid but semantically wrong
Some instantiations compile but produce nonsensical results:
#include <iostream>
template <typename T>
T addOne(T x)
{
return x + 1;
}
int main()
{
std::cout << addOne("hello") << '\n'; // Compiles but does pointer arithmetic
return 0;
}
This compiles because adding an integer to a pointer (which is what a string literal decays to) is syntactically valid. The result is pointer arithmetic, not string concatenation. The output is ello because the pointer advances past the first character.
The compiler can't detect semantic errors. It's your responsibility to use templates with appropriate types.
Summary
| Syntax | Behavior |
|---|---|
square<int>(5) |
Explicit type specification |
square<>(5) |
Deduce type, only templates considered |
square(5) |
Deduce type, prefer regular functions |
Key concepts:
- Instantiation: Generating a concrete function from a template
- Type deduction: Compiler infers template arguments from function arguments
- Deduction failure: Occurs when arguments suggest conflicting types
- Templates compile only if the generated code is valid for that type
- Regular functions are preferred over templates when both match equally
Template Instantiation - Quiz
Test your understanding of the lesson.
Practice Exercises
Function Template Instantiation
Practice implicit and explicit template instantiation.
Lesson Discussion
Share your thoughts and questions
No comments yet. Be the first to share your thoughts!