Skip to content

Latest commit

 

History

History
269 lines (206 loc) · 13.4 KB

multidimensional-subscript-operator.md

File metadata and controls

269 lines (206 loc) · 13.4 KB

Multidimensional Subscript Operator (operator[])


Executive Abstract

The Multidimensional Subscript Operator (operator[]) is a C++23 language feature (introduced via proposals P2120R0 and P2576R0) that allows the array subscript operator operator[] to accept multiple parameters, zero parameters, or variadic parameter packs (matrix[row, col]). By permanently replacing pre-C++23 proxy row chaining (m[r][c]) and function call operator overloading (m(r, c)), multidimensional subscripting provides native, type-safe array indexing for matrices, tensors, image buffers, and C++23's non-owning multidimensional array views (std::mdspan).


Motivation & Historical Context

Pre-C++23 Deficiencies: The Single-Parameter Subscript Limit

For over 40 years (inherited from C in 1972 and C++98), operator[] was constrained by the C++ grammar to accept strictly one parameter: T& operator[](std::size_t index).

When building multi-dimensional data structures (2D matrices, 3D spatial grids, image arrays), developers were forced into three flawed workarounds:

// Pre-C++23 Workaround 1: Chained Single-Index Proxy Objects
matrix[row][col]; // Requires operator[](row) to return a temporary ProxyRow object!

// Pre-C++23 Workaround 2: Function Call Operator Overloading
matrix(row, col); // Obscured intent! Looked like a function call, NOT array element access!

Deficiencies of Pre-C++23 Multidimensional Indexing:

  1. Proxy Object Chaining Overhead (m[r][c]): Indexing a 2D matrix as m[r][c] required operator[](r) to construct a temporary ProxyRow object, which then declared its own operator[](c). This created compiler inline barriers, register pressure, and pointer arithmetic overhead.
  2. Syntactic Domain Mis-association (m(r, c)): Overloading operator()(r, c) allowed passing multiple indices, but m(r, c) syntactically resembled function invocation rather than container element indexing.
  3. The Comma Operator Trap: In pre-C++20 C++, writing m[1, 2] evaluated 1, 2 as a standard C comma expression (evaluating 1, discarding 1, and passing 2 as the single index argument to m.operator[](2)!). C++20 proposal P1161R3 explicitly deprecated comma expressions inside [] to reclaim the syntax for C++23 multidimensional subscripting.

Solution: C++20 deprecated comma expressions inside brackets, and C++23 updated [over.sub], permitting operator[] to take arbitrary parameter lists (matrix[r, c] or tensor[x, y, z, w]).


Technical Mechanics & Grammar Specifications

1. Syntax & Variadic Parameter Packs ([over.sub])

In C++23, operator[] can be overloaded with any number of parameters:

$$\text{ReturnType } \mathbf{\text{operator[]}} \mathbf{(} \quad \text{Index}_1 \quad i_1 , , \quad \text{Index}_2 \quad i_2 , , \quad \dots \quad \text{Index}_N \quad i_N \quad \mathbf{)}$$

$$\text{template <typename... Indices> } \text{ReturnType } \mathbf{\text{operator[]}} \mathbf{(} \quad \text{Indices... indices} \quad \mathbf{)}$$

struct Matrix3D {
    // C++23 Multidimensional Subscript Operator
    double& operator[](std::size_t x, std::size_t y, std::size_t z);

    // C++23 Variadic Subscript Operator Template
    template <typename... Indices>
    double& operator[](Indices... indices);
};

2. Multi-Dimensional View Foundation: std::mdspan (C++23)

The primary C++23 Standard Library feature enabled by multidimensional operator[] is std::mdspan (<mdspan>, proposal P0009R18). std::mdspan is a non-owning $N$-dimensional view over a contiguous memory buffer:

                            std::mdspan<double, std::extents<3, 3>>
 ┌─────────────────────────────────────────────────────────────────────────────────────────┐
 │ Contiguous Memory Buffer: [ 0.0, 1.0, 2.0 | 3.0, 4.0, 5.0 | 6.0, 7.0, 8.0 ]             │
 └───────────────────────────────────────────┬─────────────────────────────────────────────┘
                                             │
                                             ▼
                                  grid[row, col] Indexing
                                  grid[1, 2] === 5.0

Evolution Across Standards (C++20 to C++26/C++29)

┌──────────────────────────────────────────────────────────────────┐
│ C++11 / C++14 / C++17                                            │
├──────────────────────────────────────────────────────────────────┤
│ • operator[] strictly limited to 1 argument                      │
│ • Multidimensional indexing required m[r][c] proxies or m(r, c)  │
└──────────────────────────────────────────────────────────────────┘
                                 │
                                 ▼
┌──────────────────────────────────────────────────────────────────┐
│ C++20                                                            │
├──────────────────────────────────────────────────────────────────┤
│ • Deprecated comma operator inside [] (P1161R3)                  │
│ • Reclaimed bracket syntax for multidimensional subscripting     │
└──────────────────────────────────────────────────────────────────┘
                                 │
                                 ▼
┌──────────────────────────────────────────────────────────────────┐
│ C++23                                                            │
├──────────────────────────────────────────────────────────────────┤
│ • Introduced Multidimensional Subscript Operator (P2120R0)       │
│ • Introduced std::mdspan in <mdspan> (P0009R18)                  │
│ • Supported zero, two, or N variadic parameters in operator[]    │
└──────────────────────────────────────────────────────────────────┘
                                 │
                                 ▼
┌──────────────────────────────────────────────────────────────────┐
│ C++26                                                            │
├──────────────────────────────────────────────────────────────────┤
│ • C++26 Pack Indexing over variadic subscript parameter packs    │
└──────────────────────────────────────────────────────────────────┘

Comprehensive Code Examples

Example 1: Custom 2D Matrix Class with Multidimensional operator[]

Demonstrates implementing a 2D matrix class with C++23 multidimensional subscript indexing.

#include <iostream>
#include <vector>
#include <cstddef>

template <typename T>
class Matrix2D {
private:
    std::size_t m_rows;
    std::size_t m_cols;
    std::vector<T> m_data;

public:
    Matrix2D(std::size_t rows, std::size_t cols)
        : m_rows(rows), m_cols(cols), m_data(rows * cols) {}

    // C++23 Multidimensional Subscript Operator (Non-Const Lvalue)
    T& operator[](std::size_t r, std::size_t c) noexcept {
        return m_data[r * m_cols + c]; // Calculates 2D row-major offset
    }

    // C++23 Multidimensional Subscript Operator (Const Lvalue)
    const T& operator[](std::size_t r, std::size_t c) const noexcept {
        return m_data[r * m_cols + c];
    }
};

int main() {
    Matrix2D<int> matrix(3, 3);

    // C++20 Deprecated m[0,0] is now VALID C++23 Multidimensional Subscripting!
    matrix[0, 0] = 10;
    matrix[1, 2] = 42;
    matrix[2, 1] = 99;

    std::cout << "Matrix[1, 2]: " << matrix[1, 2] << " (Expected: 42)\n";
    std::cout << "Matrix[2, 1]: " << matrix[2, 1] << " (Expected: 99)\n";

    return 0;
}

Example 2: Variadic $N$-Dimensional Tensor Subscripting

Demonstrates implementing a variadic operator[] that accepts arbitrary $N$-dimensional coordinate packs.

#include <iostream>
#include <array>
#include <numeric>

template <typename T, std::size_t... Dimensions>
class Tensor {
private:
    static constexpr std::size_t TotalSize = (Dimensions * ... * 1);
    std::array<T, TotalSize> m_data{};

    // Helper calculating linear index from variadic coordinate pack
    template <typename... Indices>
    constexpr std::size_t calculate_offset(Indices... indices) const noexcept {
        std::size_t offset = 0;
        std::size_t stride = 1;
        std::size_t idx_array[] = { static_cast<std::size_t>(indices)... };
        std::size_t dim_array[] = { Dimensions... };

        for (std::size_t i = sizeof...(Dimensions); i > 0; --i) {
            offset += idx_array[i - 1] * stride;
            stride *= dim_array[i - 1];
        }
        return offset;
    }

public:
    // C++23 Variadic Multidimensional Subscript Operator
    template <typename... Indices>
    requires (sizeof...(Indices) == sizeof...(Dimensions))
    constexpr T& operator[](Indices... indices) noexcept {
        return m_data[calculate_offset(indices...)];
    }
};

int main() {
    // 3D Tensor with dimensions 2x3x4
    Tensor<int, 2, 3, 4> tensor3d;

    tensor3d[1, 2, 3] = 777; // C++23 3D Subscripting!

    std::cout << "3D Tensor[1, 2, 3]: " << tensor3d[1, 2, 3] << " (Expected: 777)\n";
    return 0;
}

Example 3: Modern C++23 std::mdspan Multidimensional Array Views

Demonstrates using standard <mdspan> for zero-copy 2D memory indexing.

#include <iostream>
#include <vector>

// C++23 <mdspan> Header
#if defined(__cpp_lib_mdspan) && __cpp_lib_mdspan >= 202302L
#include <mdspan>

int main() {
    std::vector<int> raw_buffer = {1, 2, 3, 4, 5, 6}; // 2x3 contiguous grid

    // C++23 std::mdspan: Non-owning 2D view over 1D vector (2 rows, 3 columns)
    std::mdspan<int, std::extents<std::size_t, 2, 3>> grid(raw_buffer.data());

    // Multidimensional subscript indexing directly on std::mdspan!
    std::cout << "Grid[0, 2]: " << grid[0, 2] << " (Expected: 3)\n";
    std::cout << "Grid[1, 1]: " << grid[1, 1] << " (Expected: 5)\n";

    return 0;
}
#else
int main() {
    std::cout << "C++23 <mdspan> multidimensional view demonstrated.\n";
    return 0;
}
#endif

Engineering Best Practices & Common Pitfalls

  1. Use matrix[r, c] for All New Multidimensional Containers:

    • In C++23 and later, adopt matrix[r, c] for 2D/3D grids, images, and spatial data structures. It communicates array indexing semantics better than matrix(r, c).
  2. Prefer std::mdspan for Non-Owning Matrix Views:

    • Rather than creating custom matrix wrapper classes to view raw memory buffers, use standard std::mdspan (<mdspan>), which provides layout policies (layout_left, layout_right, layout_stride) out of the box.
  3. C++20 Legacy Code Migration Hazard:

    • If legacy pre-C++20 code contained comma operator expressions inside brackets (arr[a, b] intended as b), updating to C++23 will route the call to multidimensional operator[]. Wrap comma expressions in parentheses (arr[(a, b)]) if the C comma operator behavior was intended.

Relationships

  • Related to:
    • std::mdspan (C++23 P0009R18) — Standard multidimensional non-owning array view.
    • Variadic Templates — Underpins variadic operator[](Indices...) parameters.
    • Operator Overloading (operator[]) — Base subscript operator grammar.
  • Superseded by: None.
  • Supersedes: Chained single-index proxy objects (m[r][c]), function call overloading (m(r, c)), and single-index linear offset formulas.

Back to C++23 section.