Несколько аргументов в перегруженном brackets operator

Олег Ляттэ
Олег Ляттэ

Если возникает необходимость работы с многомерными структурами, то иногда приходится делать так (на примере матрицы трансформации):

class Matrix
{
public:
  float* operator[](int row);
};

Matrix m;
m[3][1] = 0.0f;

Мы используем operator[] с одним аргументом, который должен вернуть нечто (в данном случае указатель), к чему можно ещё раз применить operator[] с одним аргументом, и так до тех пор, пока не достигнется нужное количество измерений.

 

Хорошо, если это "нечто" - некий простой встроенный тип (ссылка на массив или указатель). А если это некая внутренняя структура, которую хотелось бы сохранить приватной? Приходится добавлять вспомогательные типы-хелперы, единственная задача которых - быть промежуточным звеном в цепочке квадратных скобок.

С предложенным изменением можно написать так:

class Matrix
{
public:
  float& operator[](int row, int column);
};

Matrix m;
m[3, 1] = 0.0f;

Здесь нет промежуточных типов, и не надо гадать, что происходит: один вызов оператора для получения нужного результата. А увеличение количества измерений приведёт лишь к появлению дополнительных аргументов оператора.

-1
рейтинг
22 комментария
ru.night.beast
запись m[3, 1] сломает существующий код
ru.night.beast
dix75
ru.night.beast, Да сломает, но если вспомнить, что это в основном ошибочная запись
dix75
ru.night.beast
dix75, а что делать в ситуациях, когда запись не ошибочная?
ru.night.beast
Олег Ляттэ
ru.night.beast, можно пояснить, что именно сломается? Что-то не могу сообразить.
Олег Ляттэ
ru.night.beast
Олег Ляттэ, ну, в плюсах (если оператор "," не перегружен), результатом выражения "3, 1" будет int 1. Т.е. один аргумент, а не несколько.
Сломается код, который использует "," при вычислении индекса []
ru.night.beast
Дмитрий
Для float& operator[](int row, int column);
можно оставить такой вариант записи: m[3][1] = 0.0f;
Запятая смотрелась бы лучше, но её уже задействовали под весьма сомнительную конструкцию, в которой следовало бы использовать ";". Теперь имеем ";" в for, но "," в while.
Дмитрий
nenomius
ru.night.beast, такой код заслуживает быть сломаным.
nenomius
ru.night.beast
Дмитрий, и как быть если у класса перегружен оператор с одним и с двумя параметрами?
ru.night.beast
Олег Ляттэ
ru.night.beast, как вариант - поменять семантику запятой в квадратных скобках на аналогичную той, что используется при передаче аргументов функции. Рискованное предложение, да, но вдруг комитет решит, что это изменение стоит того.
Олег Ляттэ
dix75
ru.night.beast,
Имеет смысл только в variadic template (хотя и весьма сомнительный), в остальных случаях это описка(ошибка)
dix75
ru.night.beast
dix75, перегрузкой "," можно добиться, например, передачу в operator[] многомерного индекса.
причем скобки могут использоваться не только для работы с массивами.
еще до x11 делали лямбды, где [] применялось для lazy-evaluation.
так что, если вы лично этим не пользуетесь, это не значит что все, ошибка, давайте срочно переписывать.
ru.night.beast
dix75
ru.night.beast,
Хотите сказать, что у вас много кода где вы используете перегрузку ',' для []. Если да то могу только посочувствовать. Использовать не стандартное поведение - это ваше решение, почему все должны от этого страдать.
Можно найти и придумать огромное кол-во перегрузок удобных для разработчиков,но выбивающихся из стандартной логики (например ^ для возведения в степень), но это не повод, не вводить в язык новые вещи которые интуитивно понятные для всех
dix75
ru.night.beast
dix75, я хочу сказать, что перегрузка запятой может иметь вполне осмысленное применение и привел несколько примеров. Не понимаю, когда это перегрузка запятой стала вдруг "не стандартным поведением".
Если эти самые новые вещи ломают старые правила и при этом ничего не дают взамен, то как раз таки повод.
ru.night.beast
dix75
Уважаемый ru.night.beast, вы правы, конечно operator , имеет смысл. Но в контексте его применения в квадратных скобках на первых порах меня бы удивило бы, возможно (точнее точно) я бы смог привыкнуть к такому поведению в проекте, но все же для большого количе-ва людей такое поведение не типично. Но если существовала такая же практика как для operator <<, то тут возражения нет.
Да и еще по сути разницы в использовании operator , а не operator && или еще другого нет. Это просто синтаксический сахар
dix75
ru.night.beast
dix75, дело вкуса.
мне больше нравится вариант ifnull[_1, _2, _3] чем ifnull[_1 && _2 && _3]
ну и про приоритеты операторов не стоит забывать.
ru.night.beast
Antervis
перегружаемый operator , "убивает" большинство propolsal'ов еще в зачаточном состоянии.

синтаксис a[b, c], как уже было отмечено, может сломать существующий код. Если же делать синтаксис a[b][c], то непонятно, что делать, если помимо [] с несколькими аргументами есть еще вариант с одним аргументом, который возвращает указатель/объект с перегруженным []/объект, конвертируемый в указатель. Можно конечно определить список приоритетов так, чтобы не ломался старый код.

Проще, всё-таки, решить проблему введением библиотечных многомерных range - адаптеров
Antervis
post-increment
Antervis, поясните про "убивает" proposal. Что именно он "убивает"?

Идея писать [i][j] вместо предложенного [i,j] мне не нравится в том числе и по этой причине.
post-increment
Antervis
post-increment, в том плане, что существует много предложений, которые не могут быть реализованы из-за потенциальной порчи существовующего кода, использующего operator ,
Antervis
post-increment
Мне идея кажется интересной.
Я опасаюсь, что могут возникнуть трудности при совместимости кода.
в старом коде, например, может быть такое выражение:
int m[size];
m[foo(i),i] = 1;
и это будет пониматься компилятором как последовательные вычисления через оператор "," и последующий вызов m[i]. То есть сейчас есть эквивалентность для массивов
m[foo(i),i] = 10; <===> foo(i); m[i] =10;
В вашем предложении это уже потребует другого понимания от компилятора.

В защиту можно сказать что компилятор справляется с аргументами ().
почему бы ему не справляться и с []?

Можно ввести правила которые позволят сохранить старый код вида
m[foo(i),i] = 10;
для, скажем, массивов величин и для классов, где есть оператор [] только с одним аргументом...
Но вообще я бы рекомендовал приводить такой код к виду foo(i); m[i] =10;
При этом нововведении компилятор как раз обругает подобный старый код как ошибку, так как оператор [] оператор для 2 аргументов не был определён.
post-increment
im.einio
Вместо предложенной перегрузки оператора [] вы можете использовать перегрузку оператора ().

class Matrix
{
public:
float& operator()(int row, int column);
};

Matrix m;
m(3, 1) = 0.0f;
im.einio
Олег Ляттэ
im.einio, да, так безусловно можно, и как обход проблемы можно делать именно так. Но тогда запись 'm(3, 1)' будет больше похожа на вызов функции, чем на доступ к элементу множества.
Олег Ляттэ
dmitriy@izvolov.ru
В range-v3 этот вопрос решён достаточно легко (код сильно упрощён):

operator[](slice_bounds);

struct slice_bounds
{
From from;
To to;
};

collection[{0, 10}];

Аналогично можно и с матрицами поступить.
dmitriy@izvolov.ru
Другие идеи
Группа создана, чтобы собирать предложения к стандарту C++, организовывать их внутренние обсуждения, помогать готовить их для отправки в комитет и защищать на общих собраниях в рабочей группе по С++ Международной организации по стандартизации (ISO).