работа с выражениями: как минимизировать время построения во время выполнения - PullRequest
0 голосов
/ 28 декабря 2018

У меня есть два класса, одно выражение (SE) и набор из двух выражений (ME).Пакет является самим выражением, следовательно, он может быть элементом другого пакета.

struct SE {
    SE(char id, char n) : id(id), n(n) {}

    size_t size() const { return n; }
    char *eval(char *b) const { b[0]=id; return b+1; }

    char id, n;
};

template <typename LHS>
struct ME {
    ME(const LHS& l, const SE& r) : lhs(l), rhs(r) { }

    size_t size() const { return rhs.size(); }
    char *eval(char *b) const { *b++='('; b=lhs.eval(b); *b++=','; b=rhs.eval(b); *b++=')'; return b; }

    LHS lhs;
    SE rhs;
};

Конструкция пакета выполняет простую проверку достоверности на основе элемента данных n, доступного в MEпо методу size.Метод eval выполняет некоторые вычисления, используя элемент данных id.Ни n, ни id не известны во время компиляции.

Для обоих классов я переопределяю оператор запятой, чтобы он выполнял рекурсивное объединение нескольких отдельных выражений во вложенный пакет.

auto SE::operator,(const SE& r) { return ME<SE>(*this, r); }
auto ME<LHS>::operator,(const SE& r) { return ME<ME<LHS>>(*this, r); }

Я хочу, чтобы после того, как весь пакет был построен, метод eval запускается на всем пакете.Пример:

SE('a',1);                        // prints 'a'
SE('a',1), SE('b',1);             // prints '(a,b)'
SE('a',1), SE('b',1), SE('c',1);  // prints '((a,b),c)'

Возможный способ добиться этого - использовать деструкторы классов и добавить флаг is_outer, который соответствующим образом обновляется при конструировании SE и ME.Когда какой-либо из этих классов разрушается, если флаг указывает, что это внешний класс, тогда eval срабатывает.Полная демонстрация приведена ниже.

Тестирование на godbolt простой функции demo ниже, как мне кажется, компилятор генерирует больше кода, чем строго необходимо.Хотя id и n не известны во время компиляции, последний тип выражения должен быть.Я ожидал бы, что вся конструкция связки сводится к перемещению нескольких чисел в правильное место, а затем проверяет утверждения, но, похоже, на самом деле делает гораздо больше копий.

Можно ли получить большеконструкционной части производится во время компиляции?

#include <iostream>
#include <cassert>
#include <string>
#include <sstream>
using namespace std;

// forward declaration
template <typename LHS> struct ME;

struct SE {
    SE(char id, char n) : id(id), n(n), outer(true)  {}
    SE(const SE& expr) : id(expr.id), n(expr.n), outer(false) {}

    ME<SE> operator,(const SE& r);

    size_t size() const { return n; }
    char *eval(char *b) const { b[0]=id; return b+1; }

    ~SE() { if(outer) { char b[20] = {}; char *p=eval(b); *p++='\n'; cout << b; } }

    char id, n;
    mutable bool outer;
};

template <typename LHS>
struct ME {
    ME(const LHS& l, const SE& r)
        : lhs(l), rhs(r), outer(true)  // tentatively set to true
    { l.outer = r.outer = false;  assert(l.size() == r.size()); } // reset flag for arguments
    ME(const ME<LHS>& expr)
        : lhs(expr.lhs), rhs(expr.rhs), outer(false) {}

    size_t size() const { return rhs.size(); }
    char *eval(char *b) const { *b++='('; b=lhs.eval(b); *b++=','; b=rhs.eval(b); *b++=')'; return b; }

    auto operator,(const SE& r) { return ME<ME<LHS>>(*this, r); }

    ~ME() { if(outer) { char b[20] = {}; char *p=eval(b); *p++='\n'; cout << b; } }

    LHS lhs;
    SE rhs;
    mutable bool outer;
};

ME<SE> SE::operator,(const SE& r) { return ME<SE>(*this, r); }

void demo(char a, char na, char b, char nb, char c, char nc) {
    SE(a, na), SE(b,nb), SE(c,nc);   // prints '((a,b),c)'
}

int main() {
    demo('a',1,'b',1,'c',1);
    return 0;
}

1 Ответ

0 голосов
/ 01 января 2019

Общий шаблон, который вы используете, - это шаблоны выражений.Узнать, как это делают другие, поможет.

Обычно шаблоны выражений интенсивно используют CRTP и не хранят копии.

Мне кажется, я вижу ошибки из-за копий.

Обычно берут T&& и сохраняют T& или T&&.

Обычно шаблоны выражений заканчиваются (и исполняются), когда они назначаются для цели;ты не хочешь этого.Поскольку в C ++ отсутствует функция move-from-and-destroy, вы должны проверить «не следует выполнять» (номинально) во время выполнения.

Вместо ссылок / значений и bool вы можете хранить указатели и использовать nullкак случай "не запускать".

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

...