Overloading the parenthesis operator

The parenthesis operator (operator()) is unique because it allows you to vary both the type and number of parameters. It must be implemented as a member function.

Multiple parameters with operator()

Let's create a Grid class for a simple game board where we can access elements using (row, column) notation:

#include <iostream>
#include <cassert>

class Grid
{
private:
    static constexpr int m_size{3};
    int m_data[m_size][m_size]{};

public:
    int& operator()(int row, int col)
    {
        assert(row >= 0 && row < m_size);
        assert(col >= 0 && col < m_size);
        return m_data[row][col];
    }

    const int& operator()(int row, int col) const
    {
        assert(row >= 0 && row < m_size);
        assert(col >= 0 && col < m_size);
        return m_data[row][col];
    }
};

int main()
{
    Grid board{};
    board(0, 0) = 1;
    board(1, 1) = 5;
    board(2, 2) = 9;

    std::cout << "Center: " << board(1, 1) << '\n';
    std::cout << "Corner: " << board(0, 0) << '\n';

    return 0;
}

Output:

Center: 5
Corner: 1

This is much clearer than using nested subscript operators (board[row][col])!

Zero parameters with operator()

The parenthesis operator can also take zero parameters. Let's add a reset function:

#include <iostream>
#include <cassert>

class Grid
{
private:
    static constexpr int m_size{3};
    int m_data[m_size][m_size]{};

public:
    int& operator()(int row, int col)
    {
        assert(row >= 0 && row < m_size);
        assert(col >= 0 && col < m_size);
        return m_data[row][col];
    }

    void operator()()
    {
        for (int i{0}; i < m_size; ++i)
            for (int j{0}; j < m_size; ++j)
                m_data[i][j] = 0;
    }
};

int main()
{
    Grid board{};
    board(0, 0) = 5;
    board(1, 1) = 10;

    std::cout << "Before reset: " << board(0, 0) << '\n';

    board();  // Reset the grid

    std::cout << "After reset: " << board(0, 0) << '\n';

    return 0;
}

However, using board() to reset isn't very intuitive. A better approach would be a named member function like reset() or clear(). The parenthesis operator is most useful when the operation truly feels like a function call with parameters.

Functors (function objects)

One powerful use of operator() is creating functors - objects that act like functions but can maintain state:

#include <iostream>

class Multiplier
{
private:
    int m_factor{1};

public:
    explicit Multiplier(int factor) : m_factor{factor} {}

    int operator()(int value) const
    {
        return value * m_factor;
    }
};

int main()
{
    Multiplier timesTwo{2};
    Multiplier timesTen{10};

    std::cout << timesTwo(5) << '\n';    // 10
    std::cout << timesTwo(7) << '\n';    // 14
    std::cout << timesTen(5) << '\n';    // 50
    std::cout << timesTen(7) << '\n';    // 70

    return 0;
}

Each Multiplier object behaves like a function, but maintains its own multiplication factor. This is useful when you need function-like behavior with associated data.

A more complex example

Here's a Calculator functor that accumulates results:

#include <iostream>

class Calculator
{
private:
    int m_total{0};

public:
    int operator()(int value)
    {
        m_total += value;
        return m_total;
    }

    void reset() { m_total = 0; }
    int getTotal() const { return m_total; }
};

int main()
{
    Calculator calc{};

    std::cout << calc(10) << '\n';  // 10
    std::cout << calc(5) << '\n';   // 15
    std::cout << calc(-3) << '\n';  // 12

    Calculator calc2{};
    std::cout << calc2(100) << '\n';  // 100 (independent from calc)

    return 0;
}

When to use operator()

Use operator() when:

  • You need multiple parameters to index or access data (like 2D coordinates)
  • You're creating a functor that maintains state while acting like a function
  • The operation naturally reads like a function call

Avoid operator() when:

  • A named member function would be clearer
  • The operation doesn't feel like a function call

Summary

Parenthesis operator (()): Must be implemented as a member function. Unique among operators because it can take any number of parameters of any types.

Multiple parameters: Ideal for multi-dimensional access like grid(row, col), which is clearer than nested subscripts grid[row][col].

Zero parameters: Can take no parameters, though this is less common and a named member function is usually clearer.

Functors (function objects): The most powerful use of operator() - creates objects that act like functions but maintain state between calls. Each functor instance can have different stored data while providing function-like call syntax.

When to use operator(): Use when you need multiple parameters for indexing, when creating functors that maintain state, or when the operation naturally reads like a function call. Avoid when a named member function would be clearer.

Const correctness: Like other accessors, provide both const and non-const versions when appropriate for different use cases.

The parenthesis operator is most valuable for multidimensional containers and functors, enabling intuitive syntax while maintaining the flexibility to pass multiple parameters or store state.