Ready to practice?
Sign up to access interactive coding exercises and track your progress.
Array-Style Access Operator
Enable array-style bracket access for your container types.
Overloading the subscript operator
The subscript operator ([]) allows array-style access to elements. The subscript operator must be overloaded as a member function.
Basic subscript operator overloading
Let's create a Playlist class that manages song indices:
#include <iostream>
class Playlist
{
private:
int m_trackNumbers[10]{};
public:
int& operator[](int index)
{
return m_trackNumbers[index];
}
};
int main()
{
Playlist favorites{};
favorites[0] = 42;
favorites[1] = 17;
favorites[2] = 88;
std::cout << "Track 1: " << favorites[0] << '\n';
std::cout << "Track 2: " << favorites[1] << '\n';
std::cout << "Track 3: " << favorites[2] << '\n';
return 0;
}
The subscript operator returns a reference, allowing us to both read and modify elements using favorites[index] = value syntax.
Why return a reference?
When we write favorites[2] = 88, the expression favorites[2] evaluates first. If operator[] returned by value instead of by reference, we'd get a copy of the track number, and 88 would be assigned to that temporary copy (which would then be discarded). By returning a reference, we get the actual element, and the assignment works correctly.
Const and non-const versions
What if we have a const Playlist? We need both const and non-const versions:
#include <iostream>
class Playlist
{
private:
int m_trackNumbers[10]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
public:
int& operator[](int index)
{
return m_trackNumbers[index];
}
const int& operator[](int index) const
{
return m_trackNumbers[index];
}
};
int main()
{
Playlist myList{};
myList[0] = 99; // Uses non-const version
std::cout << "Track: " << myList[0] << '\n';
const Playlist defaultList{};
// defaultList[0] = 99; // Error: can't modify const object
std::cout << "Default track: " << defaultList[0] << '\n'; // Uses const version
return 0;
}
Adding bounds checking
Unlike built-in arrays, we can add safety checks to our subscript operator:
#include <iostream>
#include <cassert>
#include <cstddef>
class Playlist
{
private:
static constexpr int m_capacity{10};
int m_trackNumbers[m_capacity]{};
public:
int& operator[](int index)
{
assert(index >= 0 && index < m_capacity);
return m_trackNumbers[index];
}
const int& operator[](int index) const
{
assert(index >= 0 && index < m_capacity);
return m_trackNumbers[index];
}
};
int main()
{
Playlist myList{};
myList[0] = 42;
myList[9] = 99;
// myList[10] = 100; // Assertion failure!
std::cout << myList[0] << '\n';
return 0;
}
Using non-integer indices
The subscript operator doesn't have to take an integer parameter. Here's an example using strings as keys:
#include <iostream>
#include <string>
#include <string_view>
#include <vector>
struct GradeEntry
{
std::string subject{};
int grade{};
};
class StudentGrades
{
private:
std::vector<GradeEntry> m_grades{};
public:
int& operator[](std::string_view subject)
{
for (auto& entry : m_grades)
{
if (entry.subject == subject)
return entry.grade;
}
m_grades.push_back(GradeEntry{std::string{subject}, 0});
return m_grades.back().grade;
}
};
int main()
{
StudentGrades student{};
student["Math"] = 95;
student["Science"] = 88;
student["History"] = 92;
std::cout << "Math: " << student["Math"] << '\n';
std::cout << "Science: " << student["Science"] << '\n';
return 0;
}
This program outputs:
Math: 95
Science: 88
Pointers and subscript operators don't mix
Be careful when using subscript operators with pointers:
Playlist* list{new Playlist{}};
// list[0] = 42; // ERROR: treats list as array of Playlist objects
(*list)[0] = 42; // Correct: dereference first, then use subscript
delete list;
Better yet, avoid raw pointers when possible and use objects directly or smart pointers.
Summary
Subscript operator ([]): Must be overloaded as a member function. Enables array-style element access for custom classes.
Return by reference: The subscript operator should return a reference (not a value) to allow both reading and modifying elements using syntax like obj[index] = value.
Const and non-const versions: Provide both T& operator[](int) for modifying elements and const T& operator[](int) const for read-only access to const objects.
Bounds checking: Unlike built-in arrays, overloaded subscript operators can include safety checks using assert() or other validation to catch invalid indices.
Non-integer indices: The subscript operator doesn't require an integer parameter - it can take any type, enabling string-based keys or other index types.
Pointers and subscript operators: Using [] on a pointer to an object applies to the pointer itself (treating it as an array), not the object. Dereference first: (*ptr)[index].
Overloading the subscript operator makes custom container classes work naturally with familiar array-like syntax, with the added benefit of being able to add bounds checking and use non-integer index types.
Array-Style Access Operator - Quiz
Test your understanding of the lesson.
Practice Exercises
Overloading the Subscript Operator
Implement operator[] for container classes to enable array-like access. Learn to provide both const and non-const versions for read and write access.
Lesson Discussion
Share your thoughts and questions
No comments yet. Be the first to share your thoughts!