Subscript and Function Call Operators
The subscript operator [] and the function call operator () allow a class object to behave like an array or like a function, respectively. Both operators are implemented as member functions. Both also allow for arguments and return any type and overload so long as that combination is appropriate for the type they mimic.
The subscript operator, This operator is useful in situations in which a class object is indexable. Typically, the operator accepts one argument (an int index) and returns a reference to the element in that index, which can be used to either set a new value in that element or to retrieve an existing one. The following shows one class with two operator[]s implemented, one for const object references and one for regular object references:
#include <cstring>
class IntArray
{
private:
int* m_data;
public:
IntArray()
: m_data{ new int[5]{} }
{
}
IntArray(const IntArray& arr)
: m_data{ new int[5] }
{
// Call this function with the provided arguments
std::memcpy(m_data, arr.m_data, sizeof(int) * 5);
}
~IntArray()
{
delete[] m_data;
}
int& operator[](int i)
{
return m_data[i];
}
const int& operator[](int i) const
{
return m_data[i];
}
};
The expression arr[0] = 10 in non const code becomes an internal call to IntArray::operator[](0), which returns an int&. Because it’s a reference, you actually modify the contents of m_data[0]. The const version of operator[] returns a const int&, thus letting you read a const IntArray, but not modify it.
The compiler will make the right call depending on whether arr is a const object.
We’re omitting range checked indexing in the implementation simply to keep the code concise. In a full container, there usually should be boundary check.
Function call operator. This operator turns an object into one you can call like a function. As with the subscript operator, this function can receive any arguments it wants and can return whatever makes sense to be returned from a ‘function call’:
class AddFiveObject
{
private:
int m_value;
public:
AddFiveObject()
: m_value{ 0 }
{
}
AddFiveObject(int value)
: m_value{ value }
{
}
int operator()(int i)
{
// Return the computed value to the caller
return i + m_value;
}
const int operator()(int first, int second)
{
return first + second + m_value;
}
};
add5(10) becomes an inside compiler call to add5.operator()(10). The function AddFiveObject::operator()(10) calculates and then returns 10 + m_value. Second argument version allows to pass some ‘configuration’.
‘Function’ objects, sometimes known as function objects or functors, is a term coined to describe this idea: an object that ‘behaves like a function.’
You will find this pattern commonly employed in standard library algorithms, for example.
Why would anyone bother doing this? Well, a function call that is associated with an object can carry its own data with it, giving it memory, whereas free functions and standalone functions can only hold current process memory. Like the subscripting operator, the operator() function that supports these calls must be defined as a member function. In a more complete scenario, any data needed for the ‘call’ would be passed in to the constructor, giving it persistent memory until the function object expires.
Exercise: Subscript and Function Call OperatorsStarter code, hints and solutionOpenClose
Practice: Subscript and Function Call Operators
Work through the mission, reveal hints only when needed, and compare your result with the model solution.
Your challenge
Add calls using scores[2] and double_value(6). Predict their results and keep the subscript within the three stored elements.
Mission checklist
- Run the starter as a baseline, then predict the change requested in Your challenge.
- Make that change in your editor; use the lesson-specific hints if you need help.
- Compile and run your answer, then compare with the worked solution and explain the result.
Starter program
This is the working baseline, not the completed answer. Compile as C++20, then make the change described above.
#include <iostream>
#include <vector>
class Scores
{
private:
std::vector<int> values{ 7, 9, 8 };
public:
int operator[](std::size_t index) const
{
return values[index];
}
};
class Doubler
{
public:
int operator()(int value) const
{
return value * 2;
}
};
int main()
{
Scores scores;
Doubler double_value;
std::cout << scores[1] << ' ' << double_value(5) << '\n';
return 0;
}Hints
scores[1] calls scores.operator[](1); double_value(5) calls double_value.operator()(5). The brackets choose an element, while the parentheses invoke a calculation.
Only indexes 0, 1, and 2 are valid for this Scores object. Change the index within that range and separately change the Doubler argument to see the two operations independently.
Worked solution
The added calls use the third score and a new doubling argument. scores[2] returns 8, and double_value(6) returns 12. Index 2 remains within the three-element vector.
#include <iostream>
#include <vector>
class Scores
{
private:
std::vector<int> values{ 7, 9, 8 };
public:
int operator[](std::size_t index) const
{
return values[index];
}
};
class Doubler
{
public:
int operator()(int value) const
{
return value * 2;
}
};
int main()
{
Scores scores;
Doubler double_value;
std::cout << scores[1] << ' ' << double_value(5) << '\n';
std::cout << "New calls: " << scores[2] << ' ' << double_value(6) << '\n';
return 0;
}
Example output
9 10
New calls: 8 12
0 comments
No comments yet. Be the first.