Daily bit(e) C++. C++23: многомерный оператор индекса
Daily bit(e) C++ #4, многомерный operator[]
из C++23, также известный как оператор индекса.
C++23 внес значительные изменения в оператор индекса. Теперь этот оператор может принимать несколько аргументов, разделенных запятой.
Раньше это было невозможно, так как до C++20 в индексах можно было использовать оператор запятой.
int v = some_array[1,2]; // до C++20, эквивалентно v = some_array[2];
Однако в C++20 использование оператора запятой в индексах было объявлено устаревшим, а затем удалено в C++23.
После этого, теперь оператор индекса может принимать несколько аргументов:
#include <string>
#include <ranges>
#include <iostream>
struct Maze
{
// operator[] теперь может принимать несколько аргументов
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,"# ### ### ####"};
for (auto ridx : std::views::iota(0, 4))
{
for (auto cidx : std::views::iota(0, 4))
{
std::cout << maze[ridx,cidx];
}
std::cout << "\n";
}
При таком подходе теперь можно представлять сложные структуры данных без использования прокси-объектов:
#include <iostream>
#include <utility>
#include <unordered_map>
// Очень простое (и не очень эргономичное)
// трехмерное хранилище
struct Sparse3D
{
// Изменяющий доступ, всегда создает ячейку
int64_t& operator[](int64_t x, int64_t y, int64_t z)
{
return store_[x][y][z];
}
// способ константного доступа, только читает
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_;
int64_t empty = 0;
};
Sparse3D data;
// создает ячейку и сохраняет в ней 20
data[0,0,0] = 20;
// читает не существующую ячейку без ее создания
std::cout << std::as_const(data)[0,1,2] << "\n";
// печатает: 0
std::cout << std::as_const(data)[0,0,0] << "\n";
// печатает: 20
В качестве побочного эффекта этого изменения оператор индекса теперь ведет себя точно так же, как оператор вызова, предоставляя нам новый и странный способ сделать объект вызываемым.
// operator[] теперь ведет себя точно так же,
// как operator(), приводя к некоторым
// "интересным случаям использования"
struct WeirdCallable
{
void operator()(const std::string& what, const std::string& where)
{
std::cout << "I will " << what << " you at the " << where << ".\n";
}
void operator[](const std::string& what, const std::string& where)
{
std::cout << "You will " << what << " me at the " << where << ".\n";
}
};
// Возможно, что-то следует запретить
// в вашем местном руководстве по стилю
WeirdCallable me;
me("greet","market");
// печатает: "I will greet you at the market."
me["hug","crossroad"];
// печатает: "You will hug me at the crossroad."
Открыть пример на Compiler Explorer.