C++ Intermediate · Lesson 17 of 27
Class templates
Generic containers and value types, template arguments and class template deduction (C++17).
Estimated time · 60–90 min
Learning goals
What you will understand
- Explain class templates in your own words
- Use it in a small robotics example
- Verify the result instead of assuming it worked
Understand the idea first
The mental model
A class template makes a whole class generic: template <typename T, std::size_t N> class RingBuffer. Its member functions are usually written in the header, because the compiler needs the full definition when it creates each instance. Non-type parameters such as N are compile-time values.
C++17 adds class template argument deduction, so you can write std::pair p{1, 2.5} or Stack s{1} without spelling the types. The standard containers and rclcpp::Publisher<MsgT> are class templates.
Guided lesson
Work through these steps
- 1.
Read the class templates example and identify what each line owns or changes.
- 2.
Create a small source file, compile with -std=c++17 and warnings enabled, and run it.
- 3.
Change one input, predict the result before running, then compare the output.
- 4.
Record one compiler or runtime error and explain the correction in your own words.
Worked example
Read it before you run it
template <typename T, std::size_t N>
// A template with a type T and a compile-time size N.
class RingBuffer {
// A fixed-capacity queue that overwrites nothing.
public:
// Public interface.
bool push(const T& v) {
// Add a value, or report that the buffer is full.
if (count_ == N) return false;
// Full: refuse.
data_[(head_ + count_) % N] = v;
// Store at the next free slot, wrapping around.
++count_; return true;
// Track the new size and report success.
}
// End of push.
std::size_t size() const { return count_; }
// Number of stored values.
private:
// Hidden data.
std::array<T, N> data_{};
// Storage lives inside the object; no heap.
std::size_t head_{0}, count_{0};
// Position of the oldest value and the count.
};
// End of the class template.
RingBuffer<double, 8> samples;
// An instance holding up to 8 doubles.What the example is doing
- 1.Read declarations from right to left: identify each value's type, name, and initial value.
- 2.Follow the data through each expression before considering syntax shortcuts.
- 3.Compile with warnings enabled; a clean build is part of the result, not an optional polish step.
Watch it run · Values in memory
See what this code does
The terminal types this lesson's practice solution and runs it. The scene shows the kind of robot behaviour that code drives. It plays by itself and loops.
Demo for Class templates. Expected result: 1110 3
Practice code
Now type it yourself
Copy the starter into practice.cpp and replace every TODO with your own code. Build and run it: g++ -std=c++17 -Wall -Wextra -Wpedantic practice.cpp -o practice -pthread && ./practice
#include <array>
#include <iostream>
template <typename T, std::size_t N>
class RingBuffer {
public:
// TODO: write push(): return false when count_ == N, otherwise store at (head_ + count_) % N and grow count_
std::size_t size() const { return count_; }
private:
std::array<T, N> data_{};
std::size_t head_{0}, count_{0};
};
int main() {
RingBuffer<int, 3> rb;
std::cout << rb.push(1) << rb.push(2) << rb.push(3) << rb.push(4) << '\n' << rb.size() << '\n';
return 0;
}Expected output
1110
3Stuck? Show the full solution (try it yourself first)
#include <array>
#include <iostream>
template <typename T, std::size_t N>
class RingBuffer {
public:
bool push(const T& v) {
if (count_ == N) return false;
data_[(head_ + count_) % N] = v;
++count_;
return true;
}
std::size_t size() const { return count_; }
private:
std::array<T, N> data_{};
std::size_t head_{0}, count_{0};
};
int main() {
RingBuffer<int, 3> rb;
std::cout << rb.push(1) << rb.push(2) << rb.push(3) << rb.push(4) << '\n' << rb.size() << '\n';
return 0;
}Expected result
How you know it worked
- After adding the shown fragment to the guided exercise with its listed headers and surrounding program, the finished program builds with -Wall -Wextra -Wpedantic.
- Changing the stated input produces the result you predicted.
Practical exercise
Apply the lesson
Finish RingBuffer with pop() returning std::optional<T>. Test with int and std::string, and make sure a full buffer refuses a push and an empty one returns nullopt.
Before you continue
- The program builds with -std=c++17 and no warnings
- You can explain the key rule without reading the example
- The normal and edge cases both have recorded results
If something goes wrong
Start at the first error. Check spelling, required #include lines, and whether the declaration appears before use.
Print the inputs and intermediate values, then check conversions, units, and collection order.