Последний пост из серии. Сегодня речь пойдет о не очень красивом, но довольно необычном способе многоаргументной индексации элементов в многомерных структурах.
Благодаря появившейся в С++11 аггрегированной инициализации, мы можем инициализировать структуры с помощью фигурных скобок:
struct Example {
int i, j;
};
Example test{1, 2};
std::cout << test.i << " " << test.j << std::endl;
// OUTPUT:
// 1 2Этим можно воспользоваться для индексирования многомерных массивов. Определяем оператор[], который принимает на вход структуру, а вызываем мы этот оператор с помощью списка инициализации, который превращается в структуру.
struct Indexes {
size_t row;
size_t col;
};
template <typename T>
struct Matrix {
Matrix() = default;
Matrix(size_t rows, size_t cols, T init)
: ROWS{rows}, COLS{cols}
, data(ROWS * COLS, init) {}
Matrix(Matrix const&) = default;
// MAGIC HERE
T& operator[](Indexes indexes) {
return data[indexes.row * COLS + indexes.col];
}
std::vector<T>& underlying_array() { return data; }
size_t row_count() const { return ROWS;}
size_t col_count() const { return COLS;}
private:
size_t ROWS;
size_t COLS;
std::vector<T> data;
};
int main() {
Matrix mtrx(4, 5, 0);
auto& interval_buffer = mtrx.underlying_array();
std::iota(interval_buffer.begin(), interval_buffer.end(), 0);
for (size_t i = 0; i < mtrx.row_count(); ++i) {
for (size_t j = 0; j < mtrx.col_count(); ++j) {
std::cout << std::setw(2) << mtrx[{i, j}] << " "; // MAGIC HERE
}
std::cout << std::endl;
}
}Получается, что мы индексируем элемент матрицы с помощью двух индексов в операторе квадратные скобки! Единственное, что надо добавлять еще и фигурные.
Не самый элегантный способ, но довольно просто реализуется.
У него есть недостаток, что нельзя переопределить опретора[] для другой структуры, которая может принимать другое число параметров.
Например, мы хотим по одному индексу получать всю строку сразу. Можно попробовать определить другую структуру с одним полем и также через {} инициализировать эту структуру в квадратных скобках.
struct Index {
size_t row;
};
template<typename T>
T& Matrix<T>::operator[](Index index) {
return ArraySpan{data.data() + index.row * COLS, COLS};
}
auto row = mtrx[{1}];Но такое не прокатит. Компилятор не сможет определить, какой конкретно оператор ему нужно вызвать.
Дело в том, что аггрегированно инициализировать типы можно и меньшим количеством аргументов. То есть с помощью {1} я могу создать и объект Index, и объект Indexes.
Поэтому способ довольно ограниченный.
Вообще, про эта небольшая серия была не про то, чтобы захейтить наследованный от С оператор[] с его одним аргументом. Я хотел показать интересные решения, до которых доходили люди в условиях ограничений стандарта. Возможно, в них было то, о чем вы никогда не думали. И это расширило ваш плюсовый кругозор.
Don't be limited. Stay cool.
#cppcore #cpp11