call non static member functions of template class from inside another class with instances of different types c++

Hi everyone, I'm having some doubts about the following code:

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
#include <iostream>
#include <boost/fusion/adapted/std_tuple.hpp>
#include <boost/fusion/algorithm/iteration/for_each.hpp>

struct AB
{
  virtual void f1() = 0;
  virtual double f2( int ) = 0;
};

template < size_t i >
struct A : public AB
{
  void f1() { std::cout << "A< " << i << " >::f1()\n"; }
  double f2( int j ) { std::cout << "A< " << i << " >::f2( " << j << " )\n"; return double(); }
};

template < >
struct A<0> : public AB
{
  void f1() { std::cout << "A< 0 >::f1()\n"; }
  double f2( int j ) { std::cout << "A< 0 >::f2( " << j << " )\n"; return double(); }
};

struct B
{
  std::tuple< A<0>, A<1> > tup;

  void f1() { std::cout << "B::f1()\n"; apply_( tup, &AB::f1 ); }
  double f2( int j ) { std::cout << "B::f2( " << j << " )\n"; apply_( tup, &AB::f2, j ); }

private:
  template < typename Seq, typename T, typename R, typename... Args >
  inline static void apply_( Seq &seq, R (T::*fm)( Args... args ), Args... args )
  {
    std::cout << "B::apply_()\n";
    boost::fusion::for_each( seq, [ &, args... ] (auto& x) { return (x.*fm)( args... ); } );
  }
};

int main()
{ 
  B b;
  
  b.f1();
  b.f2(5);
}


in this case, the template class is "A" and its non static member functions are "A<size_t>::f1()" and "A<size_t>::f2()", they are called from inside the class "B" (from inside "B::apply()") with instances of the template class A (i.e A<0> and A<1>) instanciated inside "apply()".

my first question is: is it better (from the performence or other point of views) to put "apply()" as static and pass "tup" by parameter or it would rather be better to use "tup" inside "apply()" (see below).

1
2
3
4
5
6
7
8
  template < typename T, typename R, typename... Args >
  inline void apply2_( R (T::*fm)( Args... args ), Args... args )
  {
    std::cout << "B::apply2_()\n";
    boost::fusion::for_each( tup, [ &, args... ] (auto& x) { return (x.*fm)( args... ); } );
  }

  double f2( int j ) { std::cout << "B::f2( " << j << " )\n"; apply2_( &AB::f2, j ); }


My thoughts are: since I'm using the class "B" as a container of "A" instances, there is no need for each instance of "B" to have its own function "apply()". Still, I have no idea about pros and cons!!

the second question is about the class "AB", I wonder if someone, using other c++ features, can write an equivalent code without having the need to use this parent class.

thank you in advance for your attention.

Adim
Last edited on
> since I'm using the class "B" as a container of "A" instances,
> there is no need for each instance of "B" to have its own function "apply()"

There would be only one instance of the non-static member function, shared by all instances of the class. The function is the same, the implicit this pointer would be different when invoked with different instances.


> about the class "AB" ... equivalent code without having the need to use this parent class.

The base class is really not doing very much, is it? Something like this, perhaps?

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
#include <iostream>
#include <boost/fusion/adapted/std_tuple.hpp>
#include <boost/fusion/algorithm/iteration/for_each.hpp>

template < size_t i > struct A
{
  void f1()  { std::cout << "A< " << i << " >::f1()\n"; }
  double f2( int j )  { std::cout << "A< " << i << " >::f2( " << j << " )\n"; return double(); }
};

template <> struct A<0>
{
  void f1() { std::cout << "A< 0 >::f1()\n"; }
  double f2( int j ) { std::cout << "A< 0 >::f2( " << j << " )\n"; return double(); }
};

struct B
{
  std::tuple< A<0>, A<1> > tup;

  void f1()  { std::cout << "B::f1()\n"; boost::fusion::for_each( tup, []( auto& x ) { x.f1() ; } ); }
  double f2( int j )
  {
      std::cout << "B::f2( " << j << " )\n";
      double ret_val = 0 ;
      boost::fusion::for_each( tup, [&ret_val,j]( auto& x ) { ret_val += x.f2(j); } );
      return ret_val ;
  }

  template < typename MFN, typename... ARGS > auto apply( MFN mfn, ARGS&&... args )
  { std::cout << "B::apply()\n" ; return (this->*mfn)( std::forward<ARGS>(args)... ) ; }
};

int main()
{
  B b;

  b.f1(); std::cout << '\n' ;
  b.apply( &B::f1 ) ; std::cout << "-------\n" ;

  b.f2(5); std::cout << '\n' ;
  b.apply( &B::f2, 5 ) ;
}

http://coliru.stacked-crooked.com/a/7304c7f216d06e8c
Hi JLBorges,
thank you for your reply.

my issue is trying to put the loop and the lambda in the same function, namely the apply() function. This was supposed to reduce dependency (i.e. I prefer depending on boost or on lambda or on the two of them in only one place in my code if it is possible rather than in two or more places. The code first posted does satisfy this demand, still, I have been forced to use a base class for A<i>.

the parent class AB with its virtual functions serve as an interface to the different functions A<i>::f1 (and eventually A<i>::f2), i=0, 1,...
Because, when calling apply, the type T (in the expression "R (T::*fm)( Args... args )") must be well defined, it can not be a template type A<size_t>, so I use T = AB (for example &AB::f1 or &AB::f2).
Since AB has virtual member functions, when calling one of them from inside the loop, the compiler select the appropriate function depending on each tuple's element "x".

PS. about the return values of f2 for the different tup elements, I'm trying to catch them in an std::vector<R>, I'm getting some kid of error saying "reference to void"!

1
2
3
4
5
6
7
8
9
10
  template < typename Seq, typename T, typename R, typename... Args >
  inline static std::vector<R> apply_( Seq &seq, R (T::*fm)( Args... args ), Args... args )
  {
    std::cout << "B::apply_()\n";
//    std::vector< std::add_lvalue_reference<R> > r_vec;
    std::vector<R> r_vec;
    boost::fusion::for_each( seq, [ &, args... ] (auto& x) { r_vec.push_back( (x.*fm)( args... ) ); } );

    return r_vec;
  }


Adim
> Since AB has virtual member functions, when calling one of them from inside the loop,
> the compiler select the appropriate function depending on each tuple's element "x".

Yes. There may be a concern about performance with this approach - calls through a pointer to a virtual function may not be inlined by the optimiser.


> I'm trying to catch them in an std::vector<R>, I'm getting some kid of error saying "reference to void"!

Two versions of apply_ would be required.
1
2
3
4
5
6
7
8
9
10
11
12
13
template < typename Seq, typename T, typename R, typename... Args >
typename std::enable_if< std::is_void<R>::value >::type
apply_( Seq &seq, R (T::*fm)( Args... args ), Args... args )
{ boost::fusion::for_each( seq, [fm,args...] (auto& x) { (x.*fm)( args... ); } ); }

template < typename Seq, typename T, typename R, typename... Args >
typename std::enable_if< !std::is_void<R>::value, std::vector< typename std::decay<R>::type > >::type
apply_( Seq &seq, R (T::*fm)( Args... args ), Args... args )
{
    std::vector< typename std::decay<R>::type > result ;
    boost::fusion::for_each( seq, [&] (auto& x) { result.push_back( (x.*fm) ( args... ) );  } );
    return result;
}

http://coliru.stacked-crooked.com/a/481b17fc688ae25a
so, this time, I feel like we don't have a lot of choices pursuing this approach...! One must choose between the following 2 options:
1- call boost::fusion::for_each() and use the polymorphic lambda within each function fi(), in this case I drop the apply() function and the class AB, or
2- use apply() and the parent class AB with its virtual member functions at our own risk, i.e. without having a clue about how much this may affect the performances.

PS.
- the 2 version of apply() are what I have been looking for, thank you!
- std::decay is not necessary, is it?

Adim
Last edited on
> std::decay is not necessary, is it?

It is.

For instance, the type R may be a reference; we can't have a vector of references.
http://coliru.stacked-crooked.com/a/c95a394d2925b3f1

Pedantically, the type 'typename std::decay<R>::type' must be MoveInsertable/CopyInsertable and Erasable.
Last edited on
Topic archived. No new replies allowed.