Template for calling different methods in a loop

I am working on an OpenGL c++ project that has objects that can be
drawn on the screen in a 3D environment and also need animation.

I have lots of functions as below. The code to cycle through all objects
in a linked list is the same but there might be different arguments
and the function to call from the object will always be different. Some
requiring an argument some wont. I cant find any material on youtube
or in forums that can solve this problem with templates.

Any suggestions for the below two examples?

void RenderAll(double Zoom)//Render All objects in the list
{
static int i;
static Object* MyCurrent;

MyCurrent = Head;
for(i = 0; i<Size; i++)
{
if(MyCurrent->Visible == true)
MyCurrent->Draw(Zoom);

MyCurrent = MyCurrent->GetNext();
}
};

void CustomAnimateAll(double TimeInterval)
{
static int i;
static Object* MyCurrent;

MyCurrent = Head;
for(i = 0; i<Size; i++)
{
MyCurrent->CustomAnimate(TimeInterval);
MyCurrent = MyCurrent->GetNext();
}
}
Last edited on
Your program looks like legacy C++. Is this correct?

Firstly, I have to ask why you implemented intrusive lists yourself instead of using std::list or std::vector. Unless you are an expert, the standard library's implementation is certainly better (and obviously more complete: the standard library has already solved this problem for you through std::for_each_n).

In any event, you should implement custom iterators for your intrusive list, or switch to boost::intrusive::list<T>, which is another implementation that already has solved the problem for you.

In the narrow scope of your problem, a template-based solution looks like this:

1
2
3
4
5
6
7
8
9
10
template <typename Fn>
void for_each_object(Fn fn)
{
  Object *MyCurrent = Head;
  for(int i = 0; i < Size; ++i)
  {
    fn(MyCurrent);
    MyCurrent = MyCurrent->GetNext();
  }
} 


The function argument can be any FunctionObject that accepts an Object*:
https://en.cppreference.com/w/cpp/named_req/FunctionObject
It is possible to extend this to any Callable
https://en.cppreference.com/w/cpp/named_req/Callable
i.e., so you can pass member function pointers (even in C++98), but you need to ask specifically for this (or find a library) because quite a lot of complex machinery is required (too much to type out immediately.) In modern C++, that functionality would be provided by std::function.
Last edited on
I'm 42 and am very old school c++ programmer. The code is from VS6. There are gaps in my knowledge. You brought up one with std:: and vectors. Thanks for that, I will have to look up that topic on youtube to fill in that gap.

My programming skills are sufficient to program linked lists my self but no harm in learning to make shortcuts with new technology.

May I ask you to supply the code to call the template for both examples I gave you? What I mean is, the code from main to actually call the templated version of my functions.

For example, before it was:

double timeinterval = 1;
double zoom = 2;

CustomAnimateAll(zoom);
RenderAll(timeinterval);

How do I summon them now with your template solution? Assuming I have added your above code in already.

David
Last edited on
If it's a linked list then why iterate from 0 to size? Why not:
1
2
3
for (Object *cur = Head; cur; cur = cur->GetNext()) {
    doStuff();
}
I'm 42 and am very old school c++ programmer. The code is from VS6. There are gaps in my knowledge.

If you like books, hear it from the horse's mouth:
https://isocpp.org/tour

May I ask you to supply the code to call the template for both examples I gave you?

Sure: here are a few usage examples:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
void draw(object *obj) {};
bool visible(object *obj) { return obj && obj->visible; }
void draw_with_zoom(object *obj, double zoom) {}

// __cplusplus < 201103L: true if this implementation does not conform to the
// March 2011 specification or later (i.e., we are working with legacy C++98).
#if __cplusplus < 201103L

// Simplest case: call the free function draw(obj) for each object pointer obj
// in the list.  Like a callback function
void e0() { for_each_object(draw); }

// Less simple case: call the function draw(obj) for each pointer to a visible
// object in the list, using an instance of a local struct as a function object:

// draw_visible is a const-qualified object of struct type
struct draw_visible_t {
  // The member function operator() is called when an instance of this class
  // appears on the left of parentheses containing an object*
  void operator()(object *obj) const {
    if (visible(obj))
      draw(obj);
  }
};
void e1() {
  draw_visible_t draw_visible;

  // For example:
  object *ptr = NULL;
  draw_visible(ptr);

  // Use it to draw each object in the list, if it is visible:
  for_each_object(draw_visible);
}

// Not-so-simple case: Introduce some kind of stateful API to manage a zoom
// parameter (hopefully one better than this):
static double current_zoom_;
static double set_current_zoom(double new_zoom) {
  // just std::exchange, nowadays
  double old_zoom = current_zoom_;
  current_zoom_ = new_zoom;
  return old_zoom;
}
static double draw_with_zoom_helper(object *obj) {
  draw_with_zoom(obj, current_zoom_);
}

void e2() {
  double const old_zoom = set_current_zoom(1.0);
  // draw each object with the stored zoom
  for_each_object(draw_with_zoom_helper);
  set_current_zoom(old_zoom);
}

// Most syntactically complex case: put the state into an ad-hoc function
// object:
struct draw_visible_with_zoom {
  // Provide a way to store a zoom parameter inside objects of this class
  explicit draw_visible_with_zoom(double zoom = 1.0) : zoom_(zoom) {}

  // Allow instances of this class to appear on the left of parentheses
  // containing an object*
  void operator()(object *obj) const {
    // Pass the stored zoom in:
    if (visible(obj))
      draw_with_zoom(obj, zoom_);
  }

  double zoom_;
};
void e3() {
  // draw_visible_with_zoom(1.0): temporary object of type
  // draw_visible_with_zoom.
  // Draw each object with a zoom parameter of 1.0
  for_each_object(draw_visible_with_zoom(1.0));
}

int main() {
  e0();
  e1();
  e2();
  e3();
}

#else // in C++11:
void e4() {
  double zoom = 1.0;

  // [zoom](object *obj) { ... }: lambda expression
  // very coarsely corresponding to an instance of the struct used in e3()
  // It is a function object (a closure, from FP theory)
  for_each_object([zoom](object *obj) {
    if (visible(obj))
      draw_with_zoom(obj, zoom);
  });
}
#endif 
Last edited on
The problem I see with this solution is that there is still duplication of code in regards to this:

void draw(object *obj)
{
obj->draw();
};

void animate(object *obj)
{
obj->animate();
};

void animate(object *obj)
{
obj->whatever();
};


Granted the number of lines are reduced per duplication, but I want to avoid the same number of duplication even if it's simpler. Can this portion become a template to eliminate this part of duplication in your solution? Can draw, animate and whatever methods be a T?

Notice in my code that I ultimately need functions to be called from inside the obj class. They need to be called from that instance of the object.


@dhayden The reason why I use an integer counter is because I have this idea that a while !=NULL is slower than a while < integer in a while statement. Am I right?
Last edited on
Notice in my code that I ultimately need functions to be called from inside the obj class. They need to be called from that instance of the object.

You mean you need obj->draw() rather than draw(obj): right, I noticed.

Can draw, animate and whatever methods be a T?


We can indeed write a function that accepts a pointer-to-member-function and calls it. But, to do so, we'd need to use idiosyncratic syntax (because we must supply a particular object on which to invoke the member function):
1
2
3
void call(void (object::*pmf)()) {
  my_obj->*pmf()
}

This works, but the downside is that we can pass exactly pointer-to-member-functions matching the signature and nothing else. We could template it as-is:
1
2
3
4
template <typename Pmf>
void call(Pmf pmf) {
  my_obj->*pmf(); // operator->*: indirect pointer-to-member access
}

Except then we're stuck with weird syntax, and in particular we can't ever specify the zoom like we've done above, because we cannot pass function objects like draw_visible_with_zoom (for example) to this function template: the syntax simply doesn't match.

The problem can be solved with the right library machinery.

One attractive solution is to automate the creation of function objects corresponding to member functions. The standard library has what you need in the function templates std::mem_fn, std::bind and the class template std::function:
https://en.cppreference.com/w/cpp/header/functional
Last edited on
If you have an implementation that supports C++17, just use std::invoke()
https://en.cppreference.com/w/cpp/utility/functional/invoke

For example:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
#include <iostream>
#include <functional>
#include <vector>
#include <list>
#include <deque>

struct object
{
    void draw( double zoom ) const
    { std::cout << this << " object::draw(" << zoom << ")\n" ; }

    void animate( int interval )
    { std::cout << this << " object::animate(" << interval << ")\n" ; }

    void mem_fun_foo( int a, double b ) const
    { std::cout << this << " object::mem_fun_foo(" << a << ',' << b << ")\n" ; }
};

void non_member_bar( object& obj, int a, double b )
{ std::cout << "non_member_bar(" << std::addressof(obj) << ',' << a << ',' << b << ")\n" ; }

void non_member_baz( object* obj, int a, double b ) { non_member_bar(*obj,a,b) ; }

template < typename SEQ, typename FN, typename... ARGS >
void invoke_for_each( SEQ& seq, FN fn, ARGS&&... args )
{
    for( auto&& obj : seq )
        std::invoke( std::forward<FN>(fn), obj, std::forward<ARGS>(args)... ) ;
}

int main()
{
    std::vector<object> vec(3) ;
    std::list< std::reference_wrapper<object> > lst( vec.begin(), vec.end() ) ;
    std::deque<object*> deq { std::addressof(vec[0]), std::addressof(vec[1]), std::addressof(vec[2] ) };

    invoke_for_each( vec, &object::draw, 1.23 ) ;
    std::cout << '\n' ;
    invoke_for_each( lst, &object::draw, 1.23 ) ;
    std::cout << '\n' ;
    invoke_for_each( deq, &object::draw, 1.23 ) ;

    std::cout << "--------------------------------\n" ;
    invoke_for_each( vec, &object::animate, 567 ) ;
    std::cout << '\n' ;
    invoke_for_each( lst, &object::animate, 567 ) ;
    std::cout << '\n' ;
    invoke_for_each( deq, &object::animate, 567 ) ;

    std::cout << "--------------------------------\n" ;
    invoke_for_each( vec, &object::mem_fun_foo, 1.23, 567 ) ;
    std::cout << '\n' ;
    invoke_for_each( lst, &object::mem_fun_foo, 1.34, 567 ) ;
    std::cout << '\n' ;
    invoke_for_each( deq, &object::mem_fun_foo, 1.34, 567 ) ;

    std::cout << "--------------------------------\n" ;
    invoke_for_each( vec, non_member_bar, 1.23, 567 ) ;
    std::cout << '\n' ;
    invoke_for_each( lst, non_member_bar, 1.34, 567 ) ;
    std::cout << '\n' ;
    invoke_for_each( deq, non_member_baz, 1.34, 567 ) ;
}

http://coliru.stacked-crooked.com/a/5c124a917278d24e
https://rextester.com/WLXASO86908
Topic archived. No new replies allowed.