SovranCode
Modern C++ Lesson 41
Course contents50 available · 0 planned

Get a program running

01 · C++ Introduction: What Is C++?02 · How to Install a C++ Compiler and Set Up Your Editor03 · C++ Hello World: Your First Program Explained04 · How to Compile and Run C++ Programs05 · C++ Debugging: Warnings and Breakpoints

Work with values and input

06 · C++ Variables: Initialization and Scope07 · C++ Data Types: int, double and Type Conversion08 · C++ const, constexpr and auto Explained09 · C++ Operators: Arithmetic, Comparison and Logic10 · C++ User Input: cin, getline and Validation

Control flow and functions

11 · C++ Conditions: if, else if and else12 · C++ Switch Statement: Cases, break and default13 · C++ For Loop: Syntax and Examples14 · C++ While and Do-While Loops: Examples15 · C++ Functions: Parameters and Return Values

Strings and sequences

16 · C++ Strings: std::string with Examples17 · C++ Vector: std::vector with Examples18 · C++ Arrays: std::array and Bounds Checking19 · C++ Range-Based For Loop and Iterators20 · C++ Vector Practice: Build a Data Summary

Model data with classes

21 · C++ Structs and enum class Explained22 · C++ Classes and Objects: Encapsulation23 · C++ Constructors and Member Initialization24 · C++ Const Member Functions and Composition25 · C++ Copying Objects and the Rule of Zero

Lifetime and resource ownership

26 · C++ Object Lifetime, Destructors and RAII27 · C++ Pointers and References: nullptr and Borrowing28 · C++ unique_ptr: Smart Pointers and Ownership29 · C++ Move Semantics: std::move vs Copying30 · C++ string_view and span: Avoid Dangling Views

Containers and algorithms

31 · C++ map vs unordered_map: Lookup and Examples32 · C++ set, queue and stack: When to Use Each33 · C++ Lambda Expressions and STL Algorithms34 · C++ sort and binary_search: Rules and Complexity35 · C++20 Ranges: Views, filter and transform

Errors, files, and interfaces

36 · C++ optional and variant Explained37 · C++ Exceptions: try, catch and Safe Cleanup38 · C++ File Handling: Read and Write Text Files39 · C++ Input Parsing and Data Validation40 · C++ Polymorphism: Virtual Functions and Interfaces

Generic programming and quality

41 · C++ Templates: Function and Class Examples42 · C++20 Concepts and Template Constraints43 · C++ Unit Testing: Normal and Boundary Cases44 · C++ Sanitizers: Find Memory and Undefined Behavior Bugs45 · C++ Performance: Benchmark Before Optimizing

Build and deliver a complete application

46 · C++ Header Files and Translation Units47 · C++ CMake Tutorial: Build an Executable48 · C++ Dependencies and CTest: Automated Tests49 · C++ Multithreading: Threads and Data Races50 · C++ Command-Line Applications: Help and Release Checks
Modern C++50 available · 0 planned

Get a program running

01 · C++ Introduction: What Is C++?02 · How to Install a C++ Compiler and Set Up Your Editor03 · C++ Hello World: Your First Program Explained04 · How to Compile and Run C++ Programs05 · C++ Debugging: Warnings and Breakpoints

Work with values and input

06 · C++ Variables: Initialization and Scope07 · C++ Data Types: int, double and Type Conversion08 · C++ const, constexpr and auto Explained09 · C++ Operators: Arithmetic, Comparison and Logic10 · C++ User Input: cin, getline and Validation

Control flow and functions

11 · C++ Conditions: if, else if and else12 · C++ Switch Statement: Cases, break and default13 · C++ For Loop: Syntax and Examples14 · C++ While and Do-While Loops: Examples15 · C++ Functions: Parameters and Return Values

Strings and sequences

16 · C++ Strings: std::string with Examples17 · C++ Vector: std::vector with Examples18 · C++ Arrays: std::array and Bounds Checking19 · C++ Range-Based For Loop and Iterators20 · C++ Vector Practice: Build a Data Summary

Model data with classes

21 · C++ Structs and enum class Explained22 · C++ Classes and Objects: Encapsulation23 · C++ Constructors and Member Initialization24 · C++ Const Member Functions and Composition25 · C++ Copying Objects and the Rule of Zero

Lifetime and resource ownership

26 · C++ Object Lifetime, Destructors and RAII27 · C++ Pointers and References: nullptr and Borrowing28 · C++ unique_ptr: Smart Pointers and Ownership29 · C++ Move Semantics: std::move vs Copying30 · C++ string_view and span: Avoid Dangling Views

Containers and algorithms

31 · C++ map vs unordered_map: Lookup and Examples32 · C++ set, queue and stack: When to Use Each33 · C++ Lambda Expressions and STL Algorithms34 · C++ sort and binary_search: Rules and Complexity35 · C++20 Ranges: Views, filter and transform

Errors, files, and interfaces

36 · C++ optional and variant Explained37 · C++ Exceptions: try, catch and Safe Cleanup38 · C++ File Handling: Read and Write Text Files39 · C++ Input Parsing and Data Validation40 · C++ Polymorphism: Virtual Functions and Interfaces

Generic programming and quality

41 · C++ Templates: Function and Class Examples42 · C++20 Concepts and Template Constraints43 · C++ Unit Testing: Normal and Boundary Cases44 · C++ Sanitizers: Find Memory and Undefined Behavior Bugs45 · C++ Performance: Benchmark Before Optimizing

Build and deliver a complete application

46 · C++ Header Files and Translation Units47 · C++ CMake Tutorial: Build an Executable48 · C++ Dependencies and CTest: Automated Tests49 · C++ Multithreading: Threads and Data Races50 · C++ Command-Line Applications: Help and Release Checks
PREVIOUS LESSONC++ Polymorphism: Virtual Functions and Interfaces
NEXT LESSONC++20 Concepts and Template Constraints
C++ foundations · Lesson 41 About 48 min with practice

C++ Templates: Function and Class Examples

Learn C++ function and class templates in simple steps, understand type deduction and requirements, repair mismatched arguments, and test the same pattern with integers and strings.

Before you begin

Know functions, classes, strings, const and value returns. Templates are checked patterns, not a way to avoid thinking about types.

What you will learn

  • Read a template parameter as a type chosen for one use.
  • Use a function template with compatible arguments.
  • Explain a failed deduction without hiding it with a cast.
  • Build a small class template that stores two values.
  • State the comparison and copying requirements of the pattern.

Reuse a pattern when the types share the needed operations

You might write one function to choose the larger of two integers, then notice that the same comparison pattern works for two strings. A function template describes that pattern with a type parameter. The compiler uses a suitable type for a particular call and checks the required operations. This is different from storing any possible value in an untyped box and deciding what it means only when the program runs.

The spelling template<typename T> introduces T as a type parameter. In the function below, T appears in the return type and in both parameter types. For a call with two integers, T can be int. For two std::string values, T can be std::string. The name T is only a name; it does not stand for one special built-in C++ type. You can choose a more descriptive parameter name when that helps the reader.

A template is useful only when its operations make sense for the chosen type. Our larger function uses less-than and returns a value copy. A type without a suitable comparison or copy operation may not work with this pattern. Write these requirements in plain words before trying a long diagnostic. Generic code still has rules. A reusable function that does the wrong thing consistently for many types is not a useful abstraction.

One larger-type pattern is used separately for integers three and seven, producing seven, and for the strings apple and pear, producing pear by lexical comparison. The diagram shows distinct checked type uses, not a runtime conversion between numbers and words.
The same source pattern can be checked for different suitable types. The result follows each type's comparison meaning: numeric order for integers and lexical character order for these lowercase strings, not word length.
C++ PRACTICE RUNNER

Use one function pattern with two types

Construct real string values for the text call. Comparing raw character pointers would ask a different question.

#include <iostream>
#include <string>
template<typename T>
T larger(T first, T second) {
    return first < second ? second : first;
}
int main() {
    std::cout << larger(3, 7) << '\n';
    std::cout << larger(std::string{"apple"}, std::string{"pear"}) << '\n';
}
TERMINAL OUTPUT
Predict the output, edit the source, then select Run code.

C++20 compiler runner: source and input are sent only when you press Run to this website's execution endpoint, not an external vendor. Local preview requires Docker and gcc:14; production requires a separately isolated execution service. Network is disabled; execution is limited to 3 seconds, compilation to 10 seconds, memory to 384 MB and output to 32 KB. Single-file programs only. Don't enter secrets. Edits reset on refresh; Copy your work to keep it.

Compare with the starter's expected output
7
pear

After editing inputs, calculate a new prediction; this reference describes only the starter.

Read one call before generalizing

For larger(3, 7), both arguments are int, so deduction chooses T as int. The function compares three with seven and returns seven. For the second call, both arguments are string objects. Their comparison is lexical: it follows character order rather than comparing lengths. With the permitted lowercase English words in this lesson, apple comes before pear. The function returns pear as a string value.

Type deduction means the compiler works out a template argument from the call's argument types. It is not a search for every conversion that could make unrelated arguments fit. The simple two-parameter pattern expects one T for both positions. If the arguments suggest conflicting types, deduction can fail. Read the types in the call before changing the function. A compiler error often points to a mismatch in the use, not a missing keyword in the template.

Templates are normally defined where the compiler can see their bodies when needed, often in headers in larger projects. The later source-organization lessons discuss that structure. For now, keep the complete definition above main in one file. A forward declaration alone does not automatically provide all needed generated definitions somewhere else. Also do not assume templates always make programs faster or smaller; this lesson teaches checked reuse, not a performance guarantee.

  • The first call supplies two int arguments.
  • Deduction chooses T as int and checks the comparison.
  • The second call supplies two string objects, choosing T as string.
  • Each call returns a value according to its type's comparison rule.
C++ PRACTICE RUNNER

Repair conflicting argument types deliberately

Intentional compile error. For this small value, larger<double>(2, 3.5) is a deliberate conversion choice. Do not apply that choice blindly to large integers.

template<typename T>
T larger(T first, T second) {
    return first < second ? second : first;
}
int main() {
    const auto result = larger(2, 3.5);
    return result > 0 ? 0 : 1;
}
TERMINAL OUTPUT
Predict the output, edit the source, then select Run code.

C++20 compiler runner: source and input are sent only when you press Run to this website's execution endpoint, not an external vendor. Local preview requires Docker and gcc:14; production requires a separately isolated execution service. Network is disabled; execution is limited to 3 seconds, compilation to 10 seconds, memory to 384 MB and output to 32 KB. Single-file programs only. Don't enter secrets. Edits reset on refresh; Copy your work to keep it.

An explicit type is a decision, not a magic repair

The broken call offers int from two and double from three-point-five. The compiler cannot deduce the single T as both. One repair is to supply two values of the same intended type. Another is to write larger<double>(2, 3.5), explicitly choosing double and allowing conversion for this small exact integer. The right repair depends on what the numbers represent. Do not choose a type merely to silence the error without checking whether information is lost.

A large integer may not be represented exactly as double, and converting a fractional value to int can discard its fraction or cause other problems outside the type's range. A more advanced function can support different input types with a deliberately chosen result rule. That is a separate design, not something the simple larger function already promises. Start with one well-defined type per call and keep the arithmetic or comparison policy easy to state.

String literals need care too. Raw string literals are character arrays that can become pointers in many contexts. A less-than comparison of pointers is not a lexical word comparison. The worked example creates std::string objects so their comparison has the intended meaning. Read the actual types of the arguments instead of assuming that two things displayed as words must automatically behave like owning strings in every generic function.

C++ PRACTICE RUNNER

Store a typed pair and reuse the function

Pair<int> and Pair<string> are different class-template uses. Each object owns its two values.

#include <iostream>
#include <string>
template<typename T>
T larger(T first, T second) {
    return first < second ? second : first;
}
template<typename T>
class Pair {
    T first_;
    T second_;
public:
    Pair(T first, T second) : first_{first}, second_{second} {}
    T largerValue() const { return larger(first_, second_); }
};
int main() {
    const Pair<int> numbers{3, 7};
    const Pair<std::string> words{"apple", "pear"};
    std::cout << numbers.largerValue() << '\n';
    std::cout << words.largerValue() << '\n';
}
TERMINAL OUTPUT
Predict the output, edit the source, then select Run code.

C++20 compiler runner: source and input are sent only when you press Run to this website's execution endpoint, not an external vendor. Local preview requires Docker and gcc:14; production requires a separately isolated execution service. Network is disabled; execution is limited to 3 seconds, compilation to 10 seconds, memory to 384 MB and output to 32 KB. Single-file programs only. Don't enter secrets. Edits reset on refresh; Copy your work to keep it.

Compare with the starter's expected output
7
pear

After editing inputs, calculate a new prediction; this reference describes only the starter.

A class template keeps the type choice visible

Pair<int> owns two integers, while Pair<string> owns two strings. The constructor initializes both members, so each object begins with its values in place. largerValue is const because observing the larger value does not change the pair. It returns a value rather than a reference to one of the function's copied local parameters. Returning a reference there would create a lifetime problem when those parameters are destroyed.

This simple pattern copies values into members and again into the helper call. That is acceptable for these small teaching examples and bounded words. Later you may improve parameter passing after checking the ownership and performance needs. Do not turn a clear safe value return into a dangling reference in the name of avoiding every copy. First establish correct behavior, then measure whether copying is a real cost for the intended workload.

The requirements are now easy to list: the selected type must be usable for these value copies, and less-than must provide the intended comparison. The two operands in each call must match the same deduced type unless you explicitly select a conversion policy. The next lesson introduces concepts for expressing some requirements in code. Even then, a compiler constraint cannot prove that your comparison matches a product's real-world meaning.

Use a concrete function when it is clearer

Not every repeated-looking function needs a template. If two types need different business rules, one generic less-than expression may hide that difference. For example, a longer word is not necessarily the lexically later word. A specific longerWord function may explain its purpose better. Choose a shared pattern only when both the operation and its meaning are genuinely shared by the types you plan to support.

For ties, our helper returns first because first < second is false. With equal integers or identical strings, the returned value looks the same either way. For richer objects, tie policy may matter and should be documented. Test both argument orders, equal values and boundary values. Do not assume a function is correct for strings merely because its integer example passed. Each intended type use deserves its own observable checks.

Test one pattern with numbers and words

Read two whole integer lines in minus one hundred through one hundred, followed by two exact word lines of one through twelve lowercase English letters. Reject missing input, invalid syntax, out-of-range numbers or unsupported word characters as Invalid input with status one. Validate all four values before output. Word lines are not trimmed. The numeric helper accepts surrounding integer whitespace, following the earlier input contract.

Implement larger<T> and Pair<T>, then create an integer pair and a string pair. Print Number and Word using largerValue. The word result follows lexical character order, not length. Test negative numbers, zero, equal numbers, equal words, a shorter word that sorts later, and maximum accepted lengths. Explain the requirements of the template and show how the intentionally mismatched earlier call is repaired. A passing lab should demonstrate understood types, not only copied angle brackets.

LOCAL TERMINAL COMMANDS

Compile and check one complete program

Save one example as main.cpp. Choose your installed compiler. Run only after a successful build. The in-page editor needs the configured compiler sandbox; these terminal commands are an alternative, not proof that the sandbox is running.

macOS / Linux:
clang++ -std=c++20 -Wall -Wextra -Wpedantic main.cpp -o lesson
# Or use g++ with the same options.
./lesson

Windows Developer Command Prompt:
cl /std:c++20 /EHsc /W4 main.cpp /Fe:lesson.exe
lesson.exe
APPLY THE CONCEPT

Independent lab

Apply one checked function-and-class pattern to bounded integers and owning strings.

Acceptance checklist

  • Read two whole integers in -100..100 and two exact lowercase-English words of length 1..12.
  • Validate all required input before output; reject invalid input with status 1.
  • Implement larger<T> using strict less-than and a value return; document the first-value tie rule.
  • Implement Pair<T> with initialized owned members and const largerValue calling the helper.
  • Print Number and Word for Pair<int> and Pair<string>; use lexical string comparison, not length.
Need a hint?
  1. Both arguments to each deduced larger call have the same type.
  2. Use actual string objects for word comparison.
  3. Return a value; do not return a reference to copied function parameters.
C++ PRACTICE RUNNER

Write and run your lab

Build your own solution before revealing the reference. Use the input field for normal, boundary and invalid cases; this runner executes your program but does not grade it.

#include <iostream>
#include <string>
#include <sstream>
#include <limits>

int main() {
    // Implement the acceptance checklist above.
    return 0;
}

TERMINAL OUTPUT
Predict the output, edit the source, then select Run code.

C++20 compiler runner: source and input are sent only when you press Run to this website's execution endpoint, not an external vendor. Local preview requires Docker and gcc:14; production requires a separately isolated execution service. Network is disabled; execution is limited to 3 seconds, compilation to 10 seconds, memory to 384 MB and output to 32 KB. Single-file programs only. Don't enter secrets. Edits reset on refresh; Copy your work to keep it.

Your lab evidence and notes

Record predictions, actual output, commands and the cause of each repair. Code is plain text: this scratchpad does not execute or grade C++.

Loading local notes…

Review your acceptance checklist and quiz explanations before marking this lesson complete. This is self-review, not a grade or certification.

Reveal the explained lab solution
C++ PRACTICE RUNNER

Reference lab solution

Compare the reference with your own reasoning. Running this solution does not grade or complete the lesson.

#include <iostream>
#include <sstream>
#include <string>
bool readInteger(int& value) {
    std::string line;
    if (!std::getline(std::cin, line)) return false;
    std::istringstream parser{line};
    if (!(parser >> value)) return false;
    parser >> std::ws;
    return parser.eof();
}
template<typename T>
T larger(T first, T second) {
    return first < second ? second : first;
}
template<typename T>
class Pair {
    T first_;
    T second_;
public:
    Pair(T first, T second) : first_{first}, second_{second} {}
    T largerValue() const { return larger(first_, second_); }
};
bool validWord(const std::string& word) {
    if (word.empty() || word.size() > 12) return false;
    const std::string letters{"abcdefghijklmnopqrstuvwxyz"};
    for (char ch : word) if (letters.find(ch) == std::string::npos) return false;
    return true;
}
int main() {
    int first{}, second{};
    std::string firstWord, secondWord;
    if (!readInteger(first) || !readInteger(second)
        || first < -100 || first > 100 || second < -100 || second > 100
        || !std::getline(std::cin, firstWord) || !std::getline(std::cin, secondWord)
        || !validWord(firstWord) || !validWord(secondWord)) {
        std::cout << "Invalid input\n"; return 1;
    }
    const Pair<int> numbers{first, second};
    const Pair<std::string> words{firstWord, secondWord};
    std::cout << "Number: " << numbers.largerValue()
              << "\nWord: " << words.largerValue() << '\n';
}
TERMINAL OUTPUT
Predict the output, edit the source, then select Run code.

C++20 compiler runner: source and input are sent only when you press Run to this website's execution endpoint, not an external vendor. Local preview requires Docker and gcc:14; production requires a separately isolated execution service. Network is disabled; execution is limited to 3 seconds, compilation to 10 seconds, memory to 384 MB and output to 32 KB. Single-file programs only. Don't enter secrets. Edits reset on refresh; Copy your work to keep it.

Expected output

Number: 7
Word: pear

The two intended types are checked through separate uses of the same pattern. There is no runtime conversion from a number into a word.

Pair owns its members and largerValue returns a value. No result refers to a helper parameter that has already been destroyed.

The helper compares by each type's less-than meaning. Bounds and exact word rules keep the lab focused without claiming to implement locale-aware dictionary ordering.

Review before moving on

Explain the lesson's objectives in your own words, compare your lab evidence with the acceptance checklist, and keep the first failing case alongside your repair. Completing a page is not proof that every C++ feature or input is supported.

  • Read a template parameter as a type chosen for one use.
  • Use a function template with compatible arguments.
  • Explain a failed deduction without hiding it with a cast.
  • Build a small class template that stores two values.
  • State the comparison and copying requirements of the pattern.

References and further reading

Original examples and exercises accompany these primary references. Tool-specific diagnostics and support depend on your installed version.

  • C++ draft: templates
  • C++ draft: function template argument deduction
  • C++ draft: template instantiation
KNOWLEDGE CHECK

Check your understanding

Answer all five questions, then review the explanations. Answers are not saved or synced; completion is a separate self-review choice.

01What does T name in template<typename T>?
02Why does larger(2, 3.5) fail deduction here?
03What does the string example compare?
04Why return T by value in this helper?
05Does a template guarantee the right business meaning for every type?
PREVIOUS LESSONC++ Polymorphism: Virtual Functions and Interfaces
NEXT LESSONC++20 Concepts and Template Constraints
ON THIS PAGELearning objectivesReuse a pattern when the types share the needed operationsRead one call before generalizingAn explicit type is a decision, not a magic repairA class template keeps the type choice visibleUse a concrete function when it is clearerTest one pattern with numbers and wordsIndependent labReviewKnowledge checkReferences
Course overview