C++ зависящие от домена встроенные операторы языка

На числовых ориентированных языках (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
7
задан Anycorn 11 April 2010 в 21:38
поделиться

6 ответов

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

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

3
ответ дан 7 December 2019 в 01:19
поделиться

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

2
ответ дан 7 December 2019 в 01:19
поделиться

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

#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

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

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

2
ответ дан 7 December 2019 в 01:19
поделиться

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

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

1
ответ дан 7 December 2019 в 01:19
поделиться

Если вы хотите повеселиться, вы можете воспользоваться 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) и возвращает прокси-объект (до последнего измерения). После применения к последнему измерению он дает представление о матрице. Нормальная матрица может быть построена из представления, имеющего те же размеры (неявный конструктор).

0
ответ дан 7 December 2019 в 01:19
поделиться

Альтернативой является создание варианта диалекта 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-операциях), и он томился на полке. Мы решили не заниматься этим на более широком рынке, потому что неясно, что это за рынок на самом деле. Я почти уверен, что есть организации, для которых это было бы полезно.

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

2
ответ дан 7 December 2019 в 01:19
поделиться
Другие вопросы по тегам:

Похожие вопросы: