46

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 OpenGL.

Precise problem:

I want an expression E; where I do not have to specify a unique name; such that a constructor is called where E is defined, and a destructor is called where the block E is in ends.

For example, consider:

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

Manual solution:

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

Now, I have this not only for glTranslate, but lots of other PushAttrib/PopAttrib calls too. I would prefer not to have to come up with a unique name for each var. Is there some trick involving macros templates ... or something else that will automatically create a variable who's constructor is called at point of definition; and destructor called at end of block?

Thanks!

Walter
  • 44,150
  • 20
  • 113
  • 196
anon
  • 41,035
  • 53
  • 197
  • 293
  • 3
    I don't see why thinking up a unique name is any harder than performing some complex macro call. –  Mar 10 '10 at 18:59
  • 6
    For what it's worth, I tried a similar scheme once upon a time. I found it was easier just making some form of `Transformation` class that had `push/pop` like you have, with member functions that make calls to translate, etc. Then you only have one class, and you're also only pushing when you need. – GManNickG Mar 10 '10 at 19:00
  • 3
    I think the answer is __LINE__ or __COUNTER__ :-) – anon Mar 10 '10 at 19:00
  • @gman: ah, good point, because I incur an extra glPushMatrix() for every non-first translate in my scheme – anon Mar 10 '10 at 19:01
  • Perhaps: `GlTranslate(1.0, 0.0, 0.0), work_to_be_done_in_block(...);` – UncleBens Mar 10 '10 at 19:09
  • @UncleBens: Could you expand on that? – GManNickG Mar 10 '10 at 19:14
  • 2
    @GMan: I was suggesting (not entirely seriously) to turn blocks into a single comma expression. AFAIK, temporaries are destructed after the full expression is evaluated (after work_to_be_done... call) => no need to give a name to the instance, not even a unique one ;) – UncleBens Mar 10 '10 at 19:24
  • @UncleBens, haha see below for the comma operator trickery xD – Johannes Schaub - litb Mar 11 '10 at 17:54

5 Answers5

68

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!

Johannes Schaub - litb
  • 496,577
  • 130
  • 894
  • 1,212
  • Does doing `bool _c_ = false` get rid of compiler warnings or something? (Over just `false`) EDIT: Derp, never-mind, I see why. What a clever trick. :] – GManNickG Mar 10 '10 at 19:08
  • 9
    @GMan I've taken this trickery from the `BOOST_FOREACH` macro :) – Johannes Schaub - litb Mar 10 '10 at 19:17
  • 2
    @Johannes Schaub - litb: I am getting a feeling that I am missing something, but what is the problem with [this](http://ideone.com/7B9lM). How is your if-for construct better? The problem is that both of these allow us to only work with one object at any particular point of scope, so we actually do need multiple different names. What did I miss? – Lazer May 09 '10 at 11:54
  • 1
    @eSKay there is no problem with your linked code. My if/for construct expands to code equivalent to your manual blocks. However, for my last solution in particular, you don't need names anymore, which is what the questioner wanted to achieve ultimately. Personally, i like to give those objects names that reflect their purpose. For example a translation that puts an object at the origin, i will do `Trans toOrigin(-x, -y, -z);` instead of just writing `Trans(-x, -y, -z)` or `Trans foo(-x, -y, -z);` which won't convey the real purpose of the transformaion. – Johannes Schaub - litb May 09 '10 at 12:33
  • Those RAII objects sharing the same name isn't a problem: The only purpose is that they do some action in their constructor, and another action in their destructor. You don't need to access the objects after they're created. So the inner one shadowing the outer one is alright. One thing the if/for solution gives you is a prettier look, since you don't need explicit braces. You can write `FOR(Trans t(a, b, c)) foo();` for example. But really, the first solution is my least preferred one. If i wanted to write no names, i would choose the last solution presented. – Johannes Schaub - litb May 09 '10 at 12:44
  • In `if(sbase const& _b_ = make_scont((E))) ;` you may want to replace the empty statement with `use_variable(_b_);` so that the compiler does not complain about `_b_` being unused (for those -Werror lovers). Pretty neat trick though, I started to use it in production code. – Alexandre C. Dec 27 '10 at 10:03
  • 3
    litb, do you work for aliens ? are you a triple agent or something ? do you recompile your brain each morning ? I feel bad. – jokoon Jan 04 '11 at 15:34
  • 3
    Johannes, your template magic always gives me crazy ideas! Thanks for this one, especially the last, I'm gonna use it for something nice... :) – Xeo Jun 01 '11 at 02:33
39

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
GManNickG
  • 494,350
  • 52
  • 494
  • 543
  • 1
    You should avoid starting identifiers with the underscore character. Names starting with underscore are reserved for the compiler, and you might get a name collision that is difficult to track down if you generate them yourself. (So, for example, replace "_trans_" with "trans_" or something more unique) – Magnus Hoff Mar 10 '10 at 20:23
  • 13
    @Magnus, GMan is fine using `_trans`. These names are only reserved in the global namespace or in the std namespace. Names that are reserved everywhere are the ones that look like `_Trans` or `__trans`. – Johannes Schaub - litb Mar 10 '10 at 20:37
  • 12
    '__LINE__ can be used as well as '__COUNTER__ – Corwin Mar 11 '10 at 09:46
  • 9
    `__LINE__` also has the advantage that you can reference the variable again in your macro. With `__COUNTER__`, it'll increment again. – zneak Aug 05 '13 at 00:06
  • 3
    @zneak: You can counteract that by injecting another macro layer that stores the generated var name. – Thomas Eding Feb 02 '15 at 08:08
  • 4
    @ThomasEding: How would you do that? – Claudiu Jun 02 '15 at 14:24
11

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

Zeks
  • 2,265
  • 20
  • 32
  • 7
    I like it, you can probably change that `std::function` to a template to ensure inlining is possible :-) – Evan Teran Jan 30 '14 at 04:00
  • @EvanTeran why is inlining possible if it is templated? – antibus Sep 28 '20 at 13:11
  • 1
    `std::function` uses type erasure techniques, which prevent the compiler from "seeing" what function was passed when it is about to generate the `GLTranslate::operator()` function. With templates, it has all the information it needs to do inlining, because (effectively), it is emmiting a custom version of the function for each unique function that is passed, and can therefore safely inline it. – Evan Teran Sep 28 '20 at 22:30
2

Using C++17, a very simple macro leading to an intuitive usage:

#define given(...) if (__VA_ARGS__; true)

And can be nested:

given (GlTranslate foo(1.0, 0.0, 0.0))
{
    foo.stuff();

    given (GlTranslate foo(1.0, 2.0, 3.0))
    {
        foo.stuff();
        ...
    }
}
Philippe
  • 422
  • 4
  • 7
0

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

yeoman
  • 1,671
  • 12
  • 15