C ++ доменные операторы встроенного языка - PullRequest
7 голосов
/ 12 апреля 2010

В языках с числовой ориентацией (Matlab, Fortran) оператор и семантика диапазона очень удобны при работе с многомерными данными. Например:

A(i:j,k,:n) // represents two-dimensional slice B(i:j,0:n)  of A at index k

к сожалению, в C ++ нет оператора диапазона (:). Конечно, его можно эмулировать с помощью функтора Range / Slice, но семантика менее чистая, чем у Matlab. Я занимаюсь прототипированием языка матрицы / тензорного домена в C ++ и мне интересно, есть ли какие-либо варианты для воспроизведения оператора диапазона. Я все еще хотел бы положиться исключительно на платформу C ++ / prprocessor.

До сих пор я просматривал волну повышения, которая может быть подходящим вариантом.

есть ли другие способы введения новых ненативных операторов в C ++ DSL?

Я знаю, что вы не можете добавить новые operator.am, специально ищущий обходной путь. Одна вещь, которую я придумал (очень уродливый взлом, и я не собираюсь использовать):

#define A(r) A[range(((1)?r), ((0)?r))] // assume A overloads []
A(i:j); // abuse ternary operator

Ответы [ 6 ]

3 голосов
/ 12 апреля 2010

Решением, которое я использовал ранее, является написание внешнего препроцессора, который анализирует источник и заменяет любое использование вашего пользовательского оператора на vanilla C ++. Для ваших целей использование a : b будет заменено чем-то вроде a.operator_range_(b), а объявления operator:() - объявлениями range_ operator_range_(). Затем в свой make-файл вы добавляете правило, которое предварительно обрабатывает исходные файлы перед их компиляцией. Это можно сделать с относительной легкостью в Perl.

Однако, работая с подобным решением в прошлом, я не рекомендую его. Он может создать проблемы с удобством обслуживания и переносимостью, если вы не будете бдительно следить за тем, как обрабатывается и генерируется источник.

2 голосов
/ 07 мая 2010

Альтернативой является создание варианта диалекта 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: один отображал значительную часть этого (помните, это была концептуальная демонстрация) на vanilla C ++, чтобы вы могли компилировать и запускать приложения Vector C ++ на обычной рабочей станции, а другой преобразование C ++ в PowerPC C ++ диалект с SIMD-расширениями инструкций, и мы сгенерировали SIMD-код, который, на наш взгляд, был довольно разумным. На это у нас ушло около 6 человеко-месяцев.

Заказчик за это в конечном итоге выручил (его бизнес-модель не включала поддержку пользовательского компилятора, несмотря на его острую потребность в параллельных / SIMD-операциях), и он томился на полке. Мы решили не заниматься этим на более широком рынке, потому что не ясно, что это за рынок на самом деле. Я уверен, что есть организации, для которых это было бы полезно.

Дело в том, что вы действительно можете это сделать. Практически невозможно использовать специальные методы. Технически это довольно просто с достаточно сильной системой преобразования программ. Это не прогулка в парке.

2 голосов
/ 12 апреля 2010

Как сказал Билли, вы не можете перегружать операторов. Однако вы можете очень близко подходить к тому, что вы хотите, с помощью «обычной» перегрузки операторов (и, возможно, некоторого метапрограммирования шаблона). Было бы довольно легко учесть что-то вроде этого:

#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;
}

Недостатком является то, что это решение не поддерживает общелитеральные выражения. Либо из, либо должны быть пользовательскими классами, поскольку мы не можем перегрузить оператор для двух примитивных типов.

Вы также можете перегрузить operator*, чтобы разрешить указывать пошаговый режим, например:

m(a-b*3,c-d); // equivalent to m[a:b:3,c:d]

И перегрузить обе версии operator--, чтобы пропустить одну из границ:

m(a--,--d); // equivalent to m[a:,:d]

Другим вариантом является определение двух объектов, называемых чем-то вроде Matrix :: start и Matrix :: end, или как вам угодно, и затем вместо использования operator-- вы можете использовать их, а затем другая граница не будет должна быть переменной и может быть литералом:

m(start-15,38-end); // This clutters the syntax however

И вы, конечно, можете использовать оба способа.

Я думаю, что это лучшее, что вы можете получить, не прибегая к причудливым решениям, таким как настраиваемые инструменты предварительной сборки или неправильное использование макросов (в том виде, в котором Матье предлагал и не рекомендовал их использовать)

2 голосов
/ 12 апреля 2010

Нет - вы не можете определить свои собственные операторы в C ++. Бьярн Страуструп подробно, почему. .

1 голос
/ 12 апреля 2010

Самое простое решение - использовать метод на матрице вместо оператора.

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[] на прокси не являются и не могут быть виртуальными.

0 голосов
/ 12 апреля 2010

Если вы хотите повеселиться, вы можете проверить 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;

Обратите внимание, что operator[] имеет только 4 перегрузки (const / non-const + basic int / range) и выдает прокси-объект (до последнего измерения). После применения к последнему измерению он дает представление о матрице. Нормальная матрица может быть построена из представления с такими же размерами (неявный конструктор).

...