|
Boost : |
From: Jeff Mirwaisi (jeff_mirwaisi_at_[hidden])
Date: 2005-02-17 03:09:25
Below I have outlined my need and implementation for a
compile-time conditional function invocation utility.
I thought it might be generally useful to others using
simmilar metaprogramming techniques. If suitable
solutions can be found for some of the outstanding
issues perhaps it will prove suitable for inclusion in
boost.
The following quick documentation and a snapshot of
the implementation can be found at
http://dump.qxxy.com/execute_if/ and (intermitantly)
http://66.1.253.185:8080/src/execute_if/index.html
Any feedback would be appreciated.
Thanks
Jeff Mirwaisi
Introduction
The following is a simple compile-time conditional
function invocation and binding utility type. Generic
code often needs to handle a subset, of all the types,
in a slightly different way than the rest.
template <typename T>
void f(T t)
{
t.a();
t.b();
...
if ( has_member_function_c<T>::value )
t.c();
}
This solution obviously will not work if type T has no
member function c. The compiler expects all the types
used with the template function f to have a member
function c even though its clear the conditional
expression is either true or false at compile time.
One possible workaround for this situation is to use
SFINAE and enable_if to overload template function f
for types which have a member function c and those
that do not.
template <typename T>
void f(T t, typename
enable_if<has_member_function_c<>,T>::type* SFINAE =
0)
{
t.a();
t.b();
...
t.c();
}
template <typename T>
void f(T t, typename
enable_if<not<has_member_function_c<T> >,T>::type*
SFINAE = 0)
{
t.a();
t.b();
...
}
This solution works! Unfortunately it forces us to
repeat the majority of the implementation of the two
template function overloads f, and it does not scale.
If we add a second conditional invocation, the number
of overloads jump to four, the same is true of
conditional callers, e.g. a second group of two
template functions overloads g if theres a single
conditional invocation, four if there are two
conditional calls etc.
To combat this overload explosion we could isolate the
conditional call to member function c with a helper
type.
template <bool>
struct call_member_function_c
{
static inline void invoke(T& t) {t.c();}
};
template <> struct call_member_function_c<false>
{
static inline void invoke(T& t) {}
};
template <typename T> void f(T t)
{
t.a();
t.b();
call_member_function_c< has_member_function_c<T>
>::invoke(t);
};
This solution improves on the previous one, it scales
well as we add conditional member function invocations
(call_member_function_d,call_member_function_e,...)
and as we increase the number of callers(f,g,h,...).
But it has introduced an explicit no-op function for
each call_member_function_X helper type, and a partial
specialization for each helper type to select whether
to generate a call or a no-op.
This "helper" type can be formalized and we can
provide a generic no-op implementing replacement for
all "helpers" which reduces our problem to the
simplest solution allowed by C++.
Target Holders
Unfortunately the limitations of the current language
wont allow us to create a generic "helper" type which
can be loaded with different template function
targets. Instead the user is expected to provide a
minimal holder type which delegates to the target
template function.
Note that the following example is also a late
template argument binding function object. This allows
the user to either invoke the conditionally executed
target immediately or produce a function object which
can be stored and called at a later point.
template <typename T> void template_fn_target(T) {}
struct template_fn_target_holder
{
typedef void result_type;
template <typename Arg>
inline result_type operator()(Arg arg) {return
template_fn_target(arg);}
template <typename Arg>
static inline result_type invoke(Arg arg) {return
template_fn_target(arg);}
};
The target holder type provides the result type of the
operation to the user, the function object usage
expects a function call overload that can handle the
arguments specified, if the call is to be made
instantly than we can simplify the use by calling a
static member function of the target holder type named
invoke.
execute_if
template <typename Condition >
struct execute_if
{
template< typename TargetHolder >
static inline typename TargetHolder::result_type
invoke( arg1,...,argN );
template< typename TargetHolder >
static inline TargetHolder bind();
};
When the condition value is true the subsequent invoke
or bind operation are performed on the target type, if
the value is false a no-op invoke or bind are
generated.
execute_if_c
template < bool >
struct execute_if_c
{
template< typename TargetHolder >
static inline typename TargetHolder::result_type
invoke( arg1,...,argN );
template< typename TargetHolder >
static inline TargetHolder bind();
};
The execute_if_c variation on the above execute_if
type provides a less generic version of enable_if
which uses a boolean value as the condition upon which
to select either the specified target or a no-op.
execute_if_else
template <typename Condition >
struct execute_if_else
{
template< typename TargetHolderA,typename
TargetHolderB >
static inline typename TargetHolder?::result_type
invoke( arg1,...,argN );
template< typename TargetHolderA,typename
TargetHolderB >
static inline TargetHolder? bind();
};
When the condition value is true the subsequent invoke
or bind operation are performed on target holder A, if
the value is false target holder B will be used
execute_if_else_c
template < bool > struct execute_if_else_c
{
template< typename TargetHolderA,typename
TargetHolderB >
static inline typename TargetHolder? ::result_type
invoke( arg1,...,argN );
template< typename TargetHolderA,typename
TargetHolderB >
static inline TargetHolder? bind();
};
The execute_if_else_c variation on the above
execute_if_else type provides a less generic version
of enable_if which uses a boolean value as the
condition upon which to select either target holder A
or target holder B.
To-Do
* Currently manually written nullary,unary,and
binary execute_if and proxy functions, needs to be
rewritten to use the boost preprocessor lib
* Possible move of the return type template
parameter into the execute_ifxxx template parameter
list (out of the bind and invoke template functions)
* Investigate using the preprocessor to
automatically generate the template function holder
types, and other techniques to reduce the users
responsibility
* Document the lack of a means of representing a
template function in the c++ type system, no function
equivalent of template template classes
* The current code also implements a set of
invoke_fn and bind_fn overloads which operate on
function references and function object references, we
must determine wether these should be provided at all,
and if so they should probably be documented
Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk