C/С++ макрос/шаблон blackmagic для генерации уникального имени

Макросы в порядке. Шаблоны в порядке. Практически все, что работает, прекрасно.

Примером является OpenGL; но эта технология имеет специфику С++ и не опирается на знание OpenGL.

Точная проблема:

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

Например, рассмотрим:

class GlTranslate {
  GLTranslate(float x, float y, float z); {
    glPushMatrix();
    glTranslatef(x, y, z);
  }
  ~GlTranslate() { glPopMatrix(); }
};

Решение вручную:

{
  GlTranslate foo(1.0, 0.0, 0.0); // I had to give it a name
  .....
} // auto popmatrix

Теперь у меня это не только для glTranslate, но и множество других PushAttrib/PopAttrib вызовов. Я бы предпочел не придумывать уникальное имя для каждого var. Есть ли какой-то трюк с шаблонами макросов... или что-то еще, что автоматически создаст переменную, конструктор которой вызывается в точке определения; и деструктор, вызванный в конце блока?

Спасибо!

Ответ 1

Если ваш компилятор поддерживает __COUNTER__ (возможно, он это делает), вы можете попробовать:

// boiler-plate
#define CONCATENATE_DETAIL(x, y) x##y
#define CONCATENATE(x, y) CONCATENATE_DETAIL(x, y)
#define MAKE_UNIQUE(x) CONCATENATE(x, __COUNTER__)

// per-transform type
#define GL_TRANSLATE_DETAIL(n, x, y, z) GlTranslate n(x, y, z)
#define GL_TRANSLATE(x, y, z) GL_TRANSLATE_DETAIL(MAKE_UNIQUE(_trans_), x, y, z)

Для

{
    GL_TRANSLATE(1.0, 0.0, 0.0);

    // becomes something like:
    GlTranslate _trans_1(1.0, 0.0, 0.0);

} // auto popmatrix

Ответ 2

Я бы не сделал этого лично, а просто придумал уникальные имена. Но если вы хотите это сделать, одним из способов является использование комбинации if и for:

#define FOR_BLOCK(DECL) if(bool _c_ = false) ; else for(DECL;!_c_;_c_=true)

Вы можете использовать его как

FOR_BLOCK(GlTranslate t(1.0, 0.0, 0.0)) {
  FOR_BLOCK(GlTranslate t(1.0, 1.0, 0.0)) {
    ...
  }
}

Каждое из этих имен находится в отдельных областях и не конфликтует. Внутренние имена скрывают внешние имена. Выражения в циклах if и for являются постоянными и должны быть легко оптимизированы компилятором.


Если вы действительно хотите передать выражение, вы можете использовать трюк ScopedGuard (см. Самый важный const), но он понадобится еще немного работы, чтобы написать его. Но хорошая сторона заключается в том, что мы можем избавиться от цикла for, и пусть наш объект оценивается до false:

struct sbase { 
  operator bool() const { return false; } 
};

template<typename T>
struct scont : sbase { 
  scont(T const& t):t(t), dismiss() { 
    t.enter();
  }
  scont(scont const&o):t(o.t), dismiss() {
    o.dismiss = true;
  }
  ~scont() { if(!dismiss) t.leave(); }

  T t; 
  mutable bool dismiss;
};

template<typename T>
scont<T> make_scont(T const&t) { return scont<T>(t); }

#define FOR_BLOCK(E) if(sbase const& _b_ = make_scont(E)) ; else

Затем вы предоставляете правильные функции enter и leave:

struct GlTranslate {
  GLTranslate(float x, float y, float z)
    :x(x),y(y),z(z) { }

  void enter() const {
    glPushMatrix();
    glTranslatef(x, y, z);
  }

  void leave() const {
    glPopMatrix();
  }

  float x, y, z;
};

Теперь вы можете полностью написать его без имени на стороне пользователя:

FOR_BLOCK(GlTranslate(1.0, 0.0, 0.0)) {
  FOR_BLOCK(GlTranslate(1.0, 1.0, 0.0)) {
    ...
  }
}

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

template<typename Derived>
struct scoped_obj { 
  void enter() const { } 
  void leave() const { } 

  Derived const& get_obj() const {
    return static_cast<Derived const&>(*this);
  }
};

template<typename L, typename R> struct collect 
  : scoped_obj< collect<L, R> > {
  L l;
  R r;

  collect(L const& l, R const& r)
    :l(l), r(r) { }
  void enter() const { l.enter(); r.enter(); }
  void leave() const { r.leave(); l.leave(); }
};

template<typename D1, typename D2> 
collect<D1, D2> operator,(scoped_obj<D1> const& l, scoped_obj<D2> const& r) {
  return collect<D1, D2>(l.get_obj(), r.get_obj());
}

#define FOR_BLOCK(E) if(sbase const& _b_ = make_scont((E))) ; else

Вам нужно наследовать объект RAII из scoped_obj<Class>, как показано ниже:

struct GLTranslate : scoped_obj<GLTranslate> {
  GLTranslate(float x, float y, float z)
    :x(x),y(y),z(z) { }

  void enter() const {
    std::cout << "entering ("
              << x << " " << y << " " << z << ")" 
              << std::endl;
  }

  void leave() const {
    std::cout << "leaving ("
              << x << " " << y << " " << z << ")" 
              << std::endl;
  }

  float x, y, z;
};

int main() {
  // if more than one element is passed, wrap them in parentheses
  FOR_BLOCK((GLTranslate(10, 20, 30), GLTranslate(40, 50, 60))) {
    std::cout << "in block..." << std::endl;
  }
}

Все они не включают виртуальных функций, а задействованные функции прозрачны для компилятора. Фактически, с вышеприведенным GLTranslate изменено на добавление единственного целого в глобальную переменную, и, оставив его снова вычитая, и нижеопределенный GLTranslateE, я сделал тест:

// we will change this and see how the compiler reacts.
int j = 0;

// only add, don't subtract again
struct GLTranslateE : scoped_obj< GLTranslateE > {
  GLTranslateE(int x):x(x) { }

  void enter() const {
    j += x;
  }

  int x;
};

int main() {
  FOR_BLOCK((GLTranslate(10), GLTranslateE(5))) {
    /* empty */
  }
  return j;
}

Фактически, GCC на уровне оптимизации -O2 выводит это:

main:
    sub     $29, $29, 8
    ldw     $2, $0, j
    add     $2, $2, 5
    stw     $2, $0, j
.L1:
    add     $29, $29, 8
    jr      $31

Я бы не ожидал, что он оптимизирован довольно хорошо!

Ответ 3

Я думаю, теперь можно сделать что-то вроде этого:

struct GlTranslate
{
    operator()(double x,double y,double z, std::function<void()> f)
    {
        glPushMatrix(); glTranslatef(x, y, z);
        f();
        glPopMatrix();
    }
};

то в коде

GlTranslate(x, y, z,[&]()
{
// your code goes here
});

Очевидно, требуется С++ 11

Ответ 4

Канонический способ, описанный в одном ответе, заключается в использовании лямбда-выражения в качестве блока, в C++ вы можете легко написать функцию шаблона.

with<T>(T instance, const std::function<void(T)> &f) {
    f(instance);
}

и использовать его как

with(GLTranslate(...), [] (auto translate) {
    ....
});

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