На числовых ориентированных языках (Matlab, Фортран) оператор диапазона и семантика очень удобны при работе с многомерными данными. Например:
A(i:j,k,:n) // represents two-dimensional slice B(i:j,0:n) of A at index k
к сожалению, C++ не имеет оператора диапазона (:). конечно, это может быть эмулировано с помощью функтора диапазона/части, но семантика является менее чистой, чем Matlab. Я моделирую язык домена матрицы/тензора в C++ и задаюсь вопросом если там любые опции воспроизвести оператор диапазона. Я все еще хотел бы полагаться на C++/prprocessor платформа исключительно.
До сих пор я просмотрел волну повышения, которая могла бы быть подходящей опцией.
там какое-либо другое средство состоит в том, чтобы представить новые несобственные операторы C++ DSL?
Я знаю, что Вы не можете добавить новый operators.am, конкретно ища обходное решение. Одна вещь я подошел (очень ужасный взлом и я не намереваемся использовать):
#define A(r) A[range(((1)?r), ((0)?r))] // assume A overloads []
A(i:j); // abuse ternary operator
Решение, которое я использовал раньше, - это написать внешний препроцессор, который анализирует исходный код и заменяет любое использование вашего пользовательского оператора на обычный C ++. Для ваших целей варианты использования a: b
должны быть заменены на что-то вроде a.operator_range_ (b)
и operator: ()
объявления с объявлениями ] диапазон_ диапазон_оператора _ ()
. Затем вы добавляете в свой make-файл правило предварительной обработки исходных файлов перед их компиляцией. В Perl это можно сделать относительно легко.
Однако, поскольку я работал с подобным решением в прошлом, я не рекомендую его. Это может создать проблемы с ремонтопригодностью и переносимостью, если вы не будете внимательно следить за тем, как исходный код обрабатывается и генерируется.
Нет - вы не можете определять свои собственные операторы в C++. Бьярне Строуструп подробно объясняет почему. .
Как сказал Билли, вы не можете перегружать операторы. Однако вы можете очень близко подойти к тому, чего хотите, с помощью «обычной» перегрузки операторов (и, возможно, некоторого метапрограммирования шаблонов). Было бы довольно легко допустить что-то вроде этого:
#include <iostream>
class FakeNumber {
int n;
public:
FakeNumber(int nn) : n(nn) {}
operator int() const { return n; }
};
class Range {
int f, t;
public:
Range(const int& ff, const int& tt) : f(ff), t(tt) {};
int from() const { return f; }
int to() const { return t; }
};
Range operator-(const FakeNumber& a, const int b) {
return Range(a,b);
}
class Matrix {
public:
void operator()(const Range& a, const Range& b) {
std::cout << "(" << a.from() << ":" << a.to() << "," << b.from() << ":" << b.to() << ")" << std::endl;
}
};
int main() {
FakeNumber a=1,b=2,c=3,d=4;
Matrix m;
m(a-b,c-d);
return 0;
}
Обратной стороной является то, что это решение не поддерживает полностью буквальные выражения. Либо from, либо должны быть пользовательскими классами, поскольку мы не можем перегрузить operator- для двух примитивных типов.
Вы также можете перегрузить оператор *
, чтобы разрешить указание пошагового режима, например:
m(a-b*3,c-d); // equivalent to m[a:b:3,c:d]
И перегрузить обе версии оператора -
, чтобы разрешить игнорирование одной из границ:
m(a--,--d); // equivalent to m[a:,:d]
Другой вариант - определить два объекта, названных что-то вроде Matrix :: start и Matrix :: end или как угодно, и затем вместо использования оператора -
вы можете использовать их, а затем другая граница не обязательно должна быть переменной и может быть буквальной:
m(start-15,38-end); // This clutters the syntax however
И, конечно, вы можете использовать оба способа.
Я думаю, что это лучшее, что вы можете получить, не прибегая к странным решениям, таким как настраиваемые инструменты предварительной сборки или злоупотребление макросами (вроде тех, которые представил Матье и которые не рекомендовал использовать :)).
Самое простое решение - использовать метод на матрице вместо оператора.
A.range(i, j, k, n);
Обратите внимание, что обычно вы не используете ,
в подстрочном операторе []
, например, A[i][j]
вместо A[i,j]
. Вторая форма может быть возможна путем перегрузки оператора запятой, но тогда вы заставите i
и j
быть объектами, а не числами.
Вы можете определить класс range
, который можно использовать в качестве субскрипта для класса матрицы.
class RealMatrix
{
public:
MatrixRowRangeProxy operator[] (int i) {
return operator[](range(i, 1));
}
MatrixRowRangeProxy operator[] (range r);
// ...
RealMatrix(const MatrixRangeProxy proxy);
};
// A generic view on a matrix
class MatrixProxy
{
protected:
RealMatrix * matrix;
};
// A view on a matrix of a range of rows
class MatrixRowRangeProxy : public MatrixProxy
{
public:
MatrixColRangeProxy operator[] (int i) {
return operator[](range(i, 1));
}
MatrixColRangeProxy operator[] (const range & r);
// ...
};
// A view on a matrix of a range of columns
class MatrixColRangeProxy : public MatrixProxy
{
public:
MatrixRangeProxy operator[] (int i) {
return operator[](range(i, 1));
}
MatrixRangeProxy operator[] (const range & r);
// ...
};
Затем вы можете скопировать диапазон из одной матрицы в другую.
RealMatrix A = ...
RealMatrix B = A[range(i,j)][range(k,n)];
Наконец, создав класс Matrix
, который может содержать либо RealMatrix
, либо MatrixProxy
, вы можете сделать так, чтобы RealMatrix
и MatrixProxy
выглядели одинаково снаружи.
Обратите внимание, что operator[]
на прокси не являются и не могут быть виртуальными.
Если вы хотите повеселиться, вы можете воспользоваться IdOp .
Если вы действительно работаете над проектом, я не предлагаю использовать этот трюк. Техническое обслуживание пострадает от хитрых уловок.
Таким образом, лучше всего не упустить шанс и использовать явную нотацию. Короткая функция, называемая range
, которая создает объект, определенный пользователем, для которого операторы перегружены, кажется особенно подходящей.
Matrix<10,30,50> matrix = /**/;
MatrixView<5,6,7> view = matrix[range(0,5)][range(0,6)][range(0,7)];
Matrix<5,6,7> n = view;
Обратите внимание, что оператор []
имеет только 4 перегрузки (const / non-const + basic int / range) и возвращает прокси-объект (до последнего измерения). После применения к последнему измерению он дает представление о матрице. Нормальная матрица может быть построена из представления, имеющего те же размеры (неявный конструктор).
Альтернативой является создание варианта диалекта C ++ с помощью инструмента преобразования программы.
DMS Software Reengineering Toolkit - это мощный промышленный механизм преобразования программ C ++ Front End . DMS, используя этот интерфейс, может анализировать полный C ++ (он даже имеет препроцессор и может сохранять большинство директив препроцессора в нерасширенном виде), автоматически создавать AST и полные таблицы символов.
Внешний интерфейс C ++ поставляется в исходном коде с использованием грамматики, полученной непосредственно из стандарта. Технически просто добавить новые правила грамматики, включая те, которые разрешают синтаксис «:» в качестве индексов массива, как вы описали и как реализовал Fortran90 +. Затем можно использовать возможность преобразования программ DMS для преобразования «нового» синтаксиса в «ванильный» C ++ для использования в обычных компиляторах C ++. (Эта схема является обобщением модели преднамеренного программирования «добавить концепции DSL к вашему языку»).
Мы фактически продемонстрировали концепцию «Vector C ++», используя этот подход.
Мы добавили многомерный тип данных Vector, семантика хранения которого заключается только в том, что элементы массива различны. Это отличается от модели последовательных расположений C ++, но вам нужна эта другая семантика, если вы хотите, чтобы компилятор / преобразователь имел свободу произвольно размещать память, и это фундаментально, если вы хотите использовать машинные инструкции SIMD и / или эффективный доступ к кешу. по разным осям.
Мы добавили доступ к скалярам и подмассивам в стиле Fortran-90, добавили практически все операции обработки массивов в F90, добавили значительную часть матричных операций APL, и все это путем корректировки грамматики DMS C ++.
Наконец, мы создали два транслятора, использующих возможности преобразования DMS: один отображает значительную часть этого (помните, это была демонстрация концепции) в обычный C ++, чтобы вы могли компилировать и запускать приложения Vector C ++ на типичной рабочей станции, а другой сопоставление C ++ с диалектом PowerPC C ++ с расширениями инструкций SIMD, и мы сгенерировали код SIMD, который, как мы думали, был довольно разумным. На все это у нас ушло около 6 человеко-месяцев.
Заказчик этого в конечном итоге выручил (его бизнес-модель не включала поддержку специального компилятора, несмотря на острую потребность в параллельных / SIMD-операциях), и он томился на полке. Мы решили не заниматься этим на более широком рынке, потому что неясно, что это за рынок на самом деле. Я почти уверен, что есть организации, для которых это было бы полезно.
Дело в том, что вы действительно можете это сделать. Практически невозможно использовать специальные методы.Технически это довольно просто с достаточно сильной системой преобразования программ. Это не прогулка по парку.