Выражение C ++ как переменная - PullRequest
1 голос
/ 27 февраля 2011

Я работаю над DSEL и хотел бы иметь следующее:

Bra ij;
Ket kl, cd;

(ij||kl); // initialize some array
(ij||cd); // ditto

....
T(i,j,k,l)*(ij||kl); // do some math without recomputing (ij||kl)

По сути, я хочу, чтобы выражение выступало в качестве переменной. Возможно ли это?

Моя идея до сих пор состоит в том, чтобы иметь "одноэлементную" фабрику, которая генерирует / просматривает массивы, используя выражение (ij|kl). Что-нибудь еще?

Ответы [ 4 ]

2 голосов
/ 27 февраля 2011

Я бы сделал это как комбинацию предложений Кена и Микаэля. Подобные вещи довольно просты с перегрузкой операторов, особенно если на каждой стороне есть пользовательские типы.

  1. Бюстгальтер :: оператор || Метод возвращает временный объект ExpressionHandle.

  2. Когда он создает ExpressionHandle, он также регистрирует ExpressionBody внутренне с глобальным хранилищем (это позволяет вам избежать необходимости использовать экземпляр переменные, но означает, что вы должны сохранить все свои выражения до конца программа или явный выпуск).

  3. Объекты ExpressionBody являются одновременно объявлением (деревом выражений) и необязательным результатом. Вы можете использовать ленивые методы, чтобы оценивать их только тогда, когда они в конце концов вызваны.

  4. Вызов ExpressionBody происходит, когда ExpressionHandle создается строкой, подобной

    Т (I, J, K, L) * (Ij || кл);

  5. (ij || kl) выше создаст ExpressionBody и попытается зарегистрировать его, но найдет существующий глобальный, поэтому просто верните ExpressionHandle, указывающий на глобальный экземпляр.

  6. operator * (const ExpressionHandle &, blah) попросит ExpressionBody вернуть свой результат, после чего он либо возвращает кэшированный результат, либо выполняет первую оценку и кэширует его.

2 голосов
/ 27 февраля 2011

Чтобы делать такие вещи, совершенно очевидно, что вам нужны какие-то глобальные знания. Другими словами, вам нужно будет создать некую глобальную или полуглобальную структуру, которая будет вести учет операций / выражений.

Я бы предложил вам построить граф (возможно, с BGL), где узлами являются выражения (переменные являются частным случаем оператора нулевой арности). Если вы соединяете каждый операнд как вершину инцидента с вершиной оператора, вы можете построить граф. Позже, когда придет время действительно оценить выражение, вы можете сначала пройти по графику с некоторыми правилами сокращения, чтобы исключить избыточные операции. Если вы удостоверитесь, что ни одна из вершин в графе не дублируется, то я думаю, что обрезка неявна.

Если вы хотите избежать использования глобальных данных, я мог бы предложить вам использовать некоторый класс администратора выражений и зарегистрировать все операции с ним. Например, вот так:

int main() {
  expression_handler expression; //have some class to handle a sequence of operations.

  expression
  << (ij||kl)
  << (ij||cd)
  << ....
  << T(i,j,k,l)*(ij||kl); //register all the expressions, in order.

  expression.evaluate(); //evaluate the expression (possibly optimizing the graph before)

  return 0;
};
2 голосов
/ 27 февраля 2011

Если вы не хотите пересчитывать ij||kl, просто сохраните их в переменную того типа, который возвращает выражение. Это как раз одна из причин существования переменных.


Хорошо, вот единственный способ, которым я могу думать об этом, хотя это звучит не очень красиво. Что вы можете сделать, это когда оператор || вызывается функция, сохраняя операнды, а также результат, в переменные экземпляра (если оператор || является функцией-членом некоторого класса) или в статически размещенную переменную (если оператор || объявлен на ней одиноким).

В следующий раз оператор || вызван, проверьте, совпадают ли операнды с последним вызовом. Если это так, просто верните последний сохраненный вами результат. В противном случае вычислите новый результат.

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

Если вы хотите пойти еще дальше, вы можете использовать map или что-то еще для хранения операндов и результатов нескольких предыдущих вызовов. Таким образом, это будет работать, если вам нужно сделать это для нескольких различных вычислений.

0 голосов
/ 27 февраля 2011

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

Вы должны сосредоточиться на том, чтобы выразить себя как можно более кратко и ясно (используете ли вы переменные или нет, полностью зависит от вас и контекста). Но если выражение использует неизмененные объекты (т. Е. Объекты, которые тоже не были назначены или изменены с использованием не затратного метода), то компилятор может оптимизировать удаление и пересчитать то же выражение.

Примечание. Чтобы помочь компилятору убедиться, что все методы помечены как const.

...