C/C++ macro/template blackmagic to generate unique name

后端 未结 4 1526
孤城傲影
孤城傲影 2020-11-29 17:29

Macros are fine. Templates are fine. Pretty much whatever it works is fine.

The example is OpenGL; but the technique is C++ specific and relies on no knowledge of Op

相关标签:
4条回答
  • 2020-11-29 17:59

    I would not do this personally but just come up with unique names. But if you want to do it, one way is to use a combination of if and for:

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

    You can use it like

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

    Each of those names are in separate scopes and won't conflict. The inner names hide the outer names. The expressions in the if and for loops are constant and should be easily optimized by the compiler.


    If you really want to pass an expression, you can use the ScopedGuard trick (see Most Important const), but it will need some more work to write it. But the nice side is, that we can get rid of the for loop, and let our object evaluate to 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
    

    You then provide the proper enter and leave functions:

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

    Now you can write it entirely without a name on the user side:

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

    If you want to pass multiple expressions at once, it's a bit more tricky, but you can write an expression template that acts on operator, to collect all expressions into a 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
    

    You need to inherit the RAII object from scoped_obj<Class> like the following shows

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

    All of these involve no virtual functions, and the functions involved are transparent to the compiler. In fact, with the above GLTranslate changed to add a single integer to a global variable and when leaving subtracting it again, and the below defined GLTranslateE, i did a test:

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

    In fact, GCC at optimization level -O2 outputs this:

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

    I wouldn't have expected that, it optimized quite well!

    0 讨论(0)
  • 2020-11-29 18:00

    The canonical way as described in one answer is to use a lambda expression as the block, in C++ you can easily write a template function

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

    and use it like

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

    but the most common reason for wanting a mechanism for avoiding defining names in your scope are long functions / methods that do lots of things. You might try a modern OOP / clean code inspired style with very short methods / functions for a change if this kind of problem keeps bothering you

    0 讨论(0)
  • 2020-11-29 18:08

    If your compiler supports __COUNTER__ (it probably does), you could try:

    // 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)
    

    For

    {
        GL_TRANSLATE(1.0, 0.0, 0.0);
    
        // becomes something like:
        GlTranslate _trans_1(1.0, 0.0, 0.0);
    
    } // auto popmatrix
    
    0 讨论(0)
  • 2020-11-29 18:09

    I think it's now possible to do something like this:

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

    then in the code

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

    Obviously, C++11 is needed

    0 讨论(0)
提交回复
热议问题