Daily bit(e) of C++ | multi-dimensional operator[]

Šimon Tóth
2 min readApr 28, 2024

Daily bit(e) of C++ ♻️4, The C++23 multi-dimensional subscript operator[].

The C++23 introduced a major change for the subscript operator, allowing it to take multiple arguments.

When multiple arguments are provided, they are separated by a comma. The standard comma operator in subscripts was deprecated in C++20.

This finally gives C++ a way to interface with multi-dimensional data structures naturally.

Because overloaded operators can have arbitrary arguments, the multi-argument operator[] can also serve as an alternative to the operator().

#include <string>
#include <ranges>
#include <utility>
#include <unordered_map>
#include <print>

struct Maze {
// operator[] can now take multiple arguments
char& operator[](size_t row, size_t col) {
if (row*width+col >= data.length())
throw std::out_of_range("out of bounds access");
return data[row*width+col];
}
const char& operator[](size_t row, size_t col) const {
if (row*width+col >= data.length())
throw std::out_of_range("out of bounds access");
return data[row*width+col];
}

size_t height;
size_t width;
std::string data;
};

Maze maze{4,4,"# ### ### ####"};

// Print the 4x4 maze
for (auto ridx : std::views::iota(0uz, 4uz)) {
for (auto cidx : std::views::iota(0uz, 4uz)) {
std::print("{}", maze[ridx,cidx]);
}
std::println("");
}



// Very simple (and not very ergonomic) 3D spare store
struct Sparse3D {
// Mutating access, always creates the cell
int64_t& operator[](int64_t x, int64_t y, int64_t z) {
return store_[x][y][z];
}
// Const access path, only reads
const int64_t& operator[](int64_t x, int64_t y, int64_t z) const {
auto xi = store_.find(x);
if (xi == store_.end()) return empty;
auto yi = xi->second.find(y);
if (yi == xi->second.end()) return empty;
auto zi = yi->second.find(z);
if (zi == yi->second.end()) return empty;
return zi->second;
}
private:
std::unordered_map<int64_t,
std::unordered_map<int64_t,
std::unordered_map<int64_t,
int64_t>>> store_;
constexpr static int64_t empty = 0;
};

Sparse3D data;
// creates the cell and stores 20 in it
data[0,0,0] = 20;

// reads an non-existent cell without creating it
auto c1 = std::as_const(data)[0,1,2];
// c1 == 0

// reads the previously stored cell
auto c2 = std::as_const(data)[0,0,0];
// c2 == 20



// the operator[] now behaves exactly as operator()
// leading to some "interesting use-cases"
struct WeirdCallable {
void operator()(
const std::string& what, const std::string& where) const {
std::println("I will {} you at the {}.", what, where);
}
void operator[](
const std::string& what, const std::string& where) const {
std::println("You will {} me at the {}.", what, where);
}
};

// Perhaps something to forbid in your local style-guide
WeirdCallable me;
me("greet","market");
// prints: "I will greet you at the market."
me["hug","crossroad"];
// prints: "You will hug me at the crossroad."

Open the example in Compiler Explorer.

--

--

Šimon Tóth

I'm an ex-Software Engineer and ex-Researcher focusing on providing free educational content.