Ready to practice?
Sign up to access interactive coding exercises and track your progress.
Multiple Template Types
Create templates with multiple independent type parameters.
The single-type limitation
Consider a template that adds two values:
template <typename T>
T add(T a, T b)
{
return a + b;
}
This works when both arguments share the same type:
add(10, 20); // OK: both int, returns 30
add(1.5, 2.5); // OK: both double, returns 4.0
But mixing types fails:
add(10, 2.5); // Error: T cannot be both int and double
The compiler cannot deduce T when arguments suggest different types. Template argument deduction looks for exact matches without performing conversions.
Three ways to handle mixed types
Approach 1: Cast the arguments
Force both arguments to the same type:
add(static_cast<double>(10), 2.5); // Both double now
This works but clutters the code.
Approach 2: Specify the type explicitly
Bypass deduction entirely:
add<double>(10, 2.5); // Instantiates add<double>, converts 10 to 10.0
When you explicitly provide the template argument, normal conversion rules apply to the function arguments.
Approach 3: Multiple template parameters
The cleanest solution uses independent type parameters:
template <typename T, typename U>
??? add(T a, U b)
{
return a + b;
}
Now T and U can resolve independently. But what should the return type be?
The return type problem
With two parameters of different types, choosing the return type is tricky:
template <typename T, typename U>
T add(T a, U b) // Returns T
{
return a + b;
}
If T is int and U is double:
add(5, 2.7); // Returns int! Result is 7, not 7.7
The addition produces 7.7 (a double), but converting to int truncates it. Using U as the return type has the same problem in reverse.
Let the compiler deduce the return type
Use auto to deduce the return type from the expression:
#include <iostream>
template <typename T, typename U>
auto add(T a, U b)
{
return a + b;
}
int main()
{
std::cout << add(5, 2.7) << '\n'; // Prints 7.7 (double)
std::cout << add(3.14, 2) << '\n'; // Prints 5.14 (double)
std::cout << add(10, 20) << '\n'; // Prints 30 (int)
return 0;
}
The compiler determines the return type from a + b using the usual arithmetic conversion rules. When mixing int and double, the result is double.
Practical examples
Calculating areas with different measurement types:
template <typename T, typename U>
auto calculateArea(T width, U height)
{
return width * height;
}
calculateArea(5, 3.5); // int * double = double
calculateArea(2.5, 4); // double * int = double
calculateArea(10, 20); // int * int = int
Interpolating between values:
template <typename T, typename U>
auto lerp(T start, U end, double t)
{
return start + (end - start) * t;
}
lerp(0, 100, 0.5); // Halfway between 0 and 100
lerp(0.0, 1.0, 0.25); // Quarter way between 0.0 and 1.0
Combining different numeric types:
template <typename T, typename U>
auto average(T a, U b)
{
return (a + b) / 2.0;
}
average(10, 20); // int, int -> double (15.0)
average(3, 4.5); // int, double -> double (3.75)
Abbreviated function templates (C++20)
C++20 provides a shorthand using auto for parameters:
auto add(auto a, auto b)
{
return a + b;
}
This is equivalent to:
template <typename T, typename U>
auto add(T a, U b)
{
return a + b;
}
Each auto parameter becomes an independent template parameter. This syntax is concise and works well for simple templates.
Limitations of abbreviated templates:
// Cannot enforce both parameters have the same type
auto process(auto x, auto y); // x and y can differ
// For same-type constraint, use traditional syntax
template <typename T>
T process(T x, T y); // x and y must match
Template overloading
Function templates can be overloaded like regular functions:
#include <iostream>
template <typename T>
auto multiply(T a, T b)
{
std::cout << "same types\n";
return a * b;
}
template <typename T, typename U>
auto multiply(T a, U b)
{
std::cout << "different types\n";
return a * b;
}
int main()
{
multiply(3, 4); // Calls single-type version
multiply(3, 4.5); // Calls two-type version
multiply(2.5, 1.5); // Calls single-type version
return 0;
}
When multiple templates could match, the compiler prefers the more specialized one. The single-parameter template is more restrictive (requires matching types), so it's preferred when applicable.
Summary
| Approach | Syntax | Best for |
|---|---|---|
| Single parameter | template <typename T> |
Enforcing same type |
| Multiple parameters | template <typename T, typename U> |
Allowing different types |
| Abbreviated (C++20) | auto func(auto, auto) |
Simple multi-type templates |
Key points:
- Template deduction doesn't perform type conversions
- Multiple template parameters allow independent type resolution
- Use
autoreturn type to avoid choosing betweenTandU - The compiler deduces return type from the actual expression
- More specialized templates are preferred during overload resolution
- C++20 abbreviated templates simplify common patterns
Multiple Template Types - Quiz
Test your understanding of the lesson.
Practice Exercises
Function Templates with Multiple Template Types
Create function templates with multiple template type parameters and auto return types.
Lesson Discussion
Share your thoughts and questions
No comments yet. Be the first to share your thoughts!