Small meta-programming exercise

M

mathieu

Hi there,

I was faced with the following issue: trying to avoid code
duplication in complexfunc1 / complexfunc2:

struct S
{
template <typename T1, typename T2>
void Foo() const { }

template <typename T1, typename T2>
void Bla(int i) const { }
};

void complexfunc1()
{
S s;
s.Foo<int,float>();
s.Foo<double,float>();
s.Foo<long,float>();
}

void complexfunc2(int i)
{
S s;
s.Bla<int,float>(i);
s.Bla<double,float>(i);
s.Bla<long,float>(i);
}


At first it looked easy but after much struggle all I could come up
with is the following solution:

template <int T> struct SHelper;
template <> struct SHelper<0>
{
template <typename T1, typename T2>
static void FooBla(S const & s, int ) { s.Foo<T1,T2>(); }
};
template <> struct SHelper<1>
{
template <typename T1, typename T2>
static void FooBla(S const & s, int i) { s.Bla<T1,T2>(i); }
};

template <int TT>
void complexfunc(int i)
{
S s;
SHelper<TT>::template FooBla<int,float>(s,i);
SHelper<TT>::template FooBla<double,float>(s,i);
SHelper<TT>::template FooBla<long,float>(s,i);
}

Am I missing something obvious or passing member function as template
parameter is not that easy.

Thanks !
-Mathieu
 
V

Victor Bazarov

mathieu said:
I was faced with the following issue: trying to avoid code
duplication in complexfunc1 / complexfunc2:

struct S
{
template <typename T1, typename T2>
void Foo() const { }

template <typename T1, typename T2>
void Bla(int i) const { }
};

void complexfunc1()
{
S s;
s.Foo<int,float>();
s.Foo<double,float>();
s.Foo<long,float>();
}

void complexfunc2(int i)
{
S s;
s.Bla<int,float>(i);
s.Bla<double,float>(i);
s.Bla<long,float>(i);
}

And how are those functions used? Are they really *that* "complex" or
are they more complex than you show here?
At first it looked easy but after much struggle all I could come up
with is the following solution:

A solution to merge the two functions? A solution to make one out of
two similar? Are they really already *that* similar?
template <int T> struct SHelper;
template <> struct SHelper<0>
{
template <typename T1, typename T2>
static void FooBla(S const & s, int ) { s.Foo<T1,T2>(); }
};
template <> struct SHelper<1>
{
template <typename T1, typename T2>
static void FooBla(S const & s, int i) { s.Bla<T1,T2>(i); }
};

template <int TT>
void complexfunc(int i)
{
S s;
SHelper<TT>::template FooBla<int,float>(s,i);
SHelper<TT>::template FooBla<double,float>(s,i);
SHelper<TT>::template FooBla<long,float>(s,i);
}

Am I missing something obvious or passing member function as template
parameter is not that easy.

Uh... And how is your 'complexfunc' used? You have avoided code
duplication in some sense, but the function is really hard to read now
and the use of it is also somewhat ugly (I suspect). What exactly was
the point? Two functions, each with four statements aren't that
expensive, are they? What's wrong with keeping them both after renaming
them to be overloaded instead of remembering '1' and '2'?

V
 
V

Victor Bazarov

mathieu said:
Hi there,

I was faced with the following issue: trying to avoid code
duplication in complexfunc1 / complexfunc2:

struct S
{
template <typename T1, typename T2>
void Foo() const { }

template <typename T1, typename T2>
void Bla(int i) const { }
};

void complexfunc1()
{
S s;
s.Foo<int,float>();
s.Foo<double,float>();
s.Foo<long,float>();
}

void complexfunc2(int i)
{
S s;
s.Bla<int,float>(i);
s.Bla<double,float>(i);
s.Bla<long,float>(i);
}


At first it looked easy but after much struggle all I could come up
with is the following solution:

template <int T> struct SHelper;
template <> struct SHelper<0>
{
template <typename T1, typename T2>
static void FooBla(S const & s, int ) { s.Foo<T1,T2>(); }
};
template <> struct SHelper<1>
{
template <typename T1, typename T2>
static void FooBla(S const & s, int i) { s.Bla<T1,T2>(i); }
};

template <int TT>
void complexfunc(int i)
{
S s;
SHelper<TT>::template FooBla<int,float>(s,i);
SHelper<TT>::template FooBla<double,float>(s,i);
SHelper<TT>::template FooBla<long,float>(s,i);
}

Am I missing something obvious or passing member function as template
parameter is not that easy.

A slightly different solution (run-time decision instead of specialization):


class Caller
{
bool m_bvoid;
int m_i;
public:
Caller() : m_bvoid(true) {}
Caller(int i) : m_bvoid(false), m_i(i) {}

template<class T1, class T2>
void call_it(S const & s) const
{
if (m_bvoid)
s.Foo<T1,T2>();
else
s.Bla<T1,T2>(m_i);
}
};

void complexfunc(Caller const& c = Caller())
{
S s;
c.call_it<int,float>(s);
c.call_it<double,float>(s);
c.call_it<long,float>(s);
}

int main()
{
complexfunc();
complexfunc(42);
}


V
 
S

Stefan Ram

mathieu said:

In the cases above, the lifetime of »s« ends after the
calls, and you control the class »S«. A more abstract
definition of the problem is not given. So in the case
given, it might be possible to define s as

S s;

in the first case and as

S s(i);

in the second case and then enter the same sequence of calls.
The instance »s« can remember how it was constructed and act
accordingly.
 
M

Michael Doubez

A slightly different solution (run-time decision instead of specialization):

class Caller
{
    bool m_bvoid;
    int m_i;
public:
    Caller() : m_bvoid(true) {}
    Caller(int i) : m_bvoid(false), m_i(i) {}

    template<class T1, class T2>
    void call_it(S const & s) const
    {
       if (m_bvoid)
          s.Foo<T1,T2>();
       else
          s.Bla<T1,T2>(m_i);
    }

};

void complexfunc(Caller const& c = Caller())
{
    S s;
    c.call_it<int,float>(s);
    c.call_it<double,float>(s);
    c.call_it<long,float>(s);

}

int main()
{
    complexfunc();
    complexfunc(42);

}

IIUC, it is the sequence <int,float>/<double,float>/<long,float> that
you want to abstract because otherwise complexfunc()/complexfunc(int
i) would be a sufficient overload.

To keep it simple, with the first parameter int/double/long, you can
use a typelist:
template <class H, class T>
struct typelist
{
typedef H head;
typedef T tail;
};

struct end_typelist {};

Then you do:

template<class TList>
class SHelper;


// End case
template <class H >
class SHelper< typelist<H, end_typelist > >
{
static void Foo(S& s)
{
s.Foo<H,float>();
}
static void Blas(S& s, int i)
{
s.Bla<H,float>(i);
}
};

// recursive case - typelist
template <class H, class T>
class SHelper< typelist<H, T> >
{
static void Foo(S& s)
{
s.Foo<H,float>();
SHelper<T>::Foo(s);
}
static void Blas(S& s, int i)
{
s.Bla<H,float>(i);
SHelper<T>::Bla(s,i);
}
};

// define types to use
typedef typelist < int,
typelist < double,
typelist < long,
end_typelist > > >
param1_type;

void complexfunc()
{
S s;
SHelper<param1_type>::Foo(s);
}

void complexfunc(int i)
{
S s;
SHelper<param1_type>::Bla(s,i);
}

I have not tried it but it should be close enough to the solution.
 
M

mathieu

  In the cases above, the lifetime of »s« ends after the
  calls, and you control the class »S«. A more abstract
  definition of the problem is not given. So in the case
  given, it might be possible to define s as

S s;

  in the first case and as

S s(i);

  in the second case and then enter the same sequence of calls.
  The instance »s« can remember how it was constructed and act
  accordingly.

That is actually a very good suggestion, I think I'll use that +
something like Victor solution.
Michael, your solution was very interesting, however my complexfunc is
really *complex*, so this is not a solution for me. I'll keep this
approach in mind anyway.

Thanks all.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,755
Messages
2,569,536
Members
45,011
Latest member
AjaUqq1950

Latest Threads

Top