Introduction
There have been several C++ delegates which declared themselves as a 'fast' or 'fastest' delegate, while Boost.Function
and its siblings, Boost.Bind
and Boost.Mem_fn
, were adopted as part of C++ Standards Committee's Library Technical Report (TR1). So, what are those called 'fast' or 'fastest' delegates and how much 'faster' are they than Boost.Function
?
The prefix 'fast' in the term 'fast(est) delegate' means either 'fast' invocation or 'fast' copy, or both. But, I believe, what is really an issue between the two when using the 'non-fast' Boost.Function
is more likely its awful copy performance. This is due to the expensive heap memory allocation that is required to store the member function and the bound object on which member function call is made. So, 'fast' delegate often refers to a delegate that does not require heap memory allocation for storing the member function and the bound object. In C++, as an object oriented programming paradigm, use of delegate or closure for the member function and the bound object is one of the most frequently occurring practices. Thus, a 'fast' delegate can 'boost' the performance by far in some situations.
The following four graphs are the result of the invocation speed comparison among three fast delegates and Boost.Function
in the various function call scenarios. See the '%FD_ROOT%/benchmark' for details.
The following two graphs are the result of the copy speed comparison among three fast delegates and Boost.Function
. For a bound member function call, it was found that Boost.Function
can take 150 times longer than the fastest. The result may vary based on the benchmark platform and environment, but it is obvious that the copy performance of Boost.Function
is not acceptable in certain cases.
In spite of the prominent speed boost of the fast delegates in specific cases, it is not comfortable for many programmers to switch and start using the fast delegates. This is because their features are not as rich as those which Boost.Function
and its siblings provide, and we are already accustomed to using Boost
s. These fast delegates support very limited types of callable entities to store and mostly do not support the storing of a function object, which is another frequently occurring practice in C++.
I had implemented a fast delegate some time ago, but it was not as fast as other fast delegates nor as C++ Standard compliant as I thought it was. I actually patched it to be C++ Standard compliant later. This is the second version, but it is completely re-implemented from the scratch. The old version is obsolete. It is another 'fast' delegate, but it is also a Boost.Function
'drop-in' replacement and more. I say 'more' because it supports the multicast feature which is missing in the most of C++ delegates currently available. It is not like an ancillary class to support multicast, but one class instance acts as single cast and multicast on demand, without any runtime performance penalty. FD.Delegate
can be thought of as an aggregation of Boost.Function
and its siblings (Boost.Bind
and Boost.Mem_fn
) plus some features from Boost.Signals
. See the 'Delegates Comparison Chart' at the end of the article for particulars.
Using the code
As stated previously, FD.Delegate
is a Boost.Function
'drop-in' replacement. So it is reasonable to refer to the online documentation of Boost.Function
and especially Boost.Function
tutorial for features of FD.Delegate
. Just make sure to add '%FD_ROOT%/include' as a system include directory.
- Example #1 from Boost.Function
.
#include <iostream>
#include <fd/delegate.hpp>
struct int_div
{
float operator()(int x, int y) const { return ((float)x)/y; };
};
int main()
{
fd::delegate<float (int, int)> f;
f = int_div();
std::cout << f(5, 3) << std::endl;
return 0;
}
- Example #2 from Boost.Function
.
#include <iostream>
#include <fd/delegate.hpp>
void do_sum_avg(int values[], int n, int& sum, float& avg)
{
sum = 0;
for (int i = 0; i < n; i++)
sum += values[i];
avg = (float)sum / n;
}
int main()
{
fd::delegate<void (int*, int, int&, float&)> sum_avg;
sum_avg = &do_sum_avg;
int values[5] = { 1, 1, 2, 3, 5 };
int sum;
float avg;
sum_avg(values, 5, sum, avg);
std::cout << "sum = " << sum << std::endl;
std::cout << "avg = " << avg << std::endl;
return 0;
}
FD.Delegate
supports multicast and uses C#'s multicast syntax, operator +=
and operator -=
.
#include <iostream>
#include <fd/delegate/delegate2.hpp>
struct print_sum
{
void operator()(int x, int y) const { std::cout << x+y << std::endl; }
};
struct print_product
{
void operator()(int x, int y) const { std::cout << x*y << std::endl; }
};
int main()
{
fd::delegate2<void, int, int> dg;
dg += print_sum();
dg += print_product();
dg(3, 5);
return 0;
}
While a function pointer is equality comparable, a function object is not quite determinant at compile-time, whether equality comparable or not. This fact makes operator -=
pretty much useless for removing a function object from multicast. FD.Delegate
has add()
and remove()
member function pairs to remedy the issue. add()
returns an instance of fd::multicast::token
which can be used to remove the added delegate(s).
#include <iostream>
#include <fd/delegate.hpp>
#include <cassert>
struct print_sum
{
void operator()(int x, int y) const { std::cout << x+y << std::endl; }
};
struct print_product
{
void operator()(int x, int y) const { std::cout << x*y << std::endl; }
};
struct print_difference
{
void operator()(int x, int y) const { std::cout << x-y << std::endl; }
};
struct print_quotient
{
void operator()(int x, int y) const { std::cout << x/-y << std::endl; }
};
int main()
{
fd::delegate2<void, int, int> dg;
dg += print_sum();
dg += print_product();
dg(3, 5);
fd::multicast::token print_diff_tok = dg.add(print_difference());
assert(print_diff_tok.valid());
dg(5, 3);
print_diff_tok.remove();
dg(5, 3);
assert(!print_diff_tok.valid());
{
fd::multicast::scoped_token t = dg.add(print_quotient());
dg(5, 3);
}
dg(5, 3);
return 0;
}
It has been one of the main concerns for a multicast delegate how to manage multiple return values. Combiner interface of Boost.Signals
has been adopted, but has slightly different usage and syntax. The type of the combiner interface is not a part of FD.Delegate
type, although it is for Boost.Signals
, as the form of the template parameter when declaring a signal variable. Instead, FD.Delegate
has a special function call operator which takes the instance of the combiner interface as the last function call argument.
#include <algorithm>
#include <iostream>
#include <fd/delegate.hpp>
template<typename T>
struct maximum
{
typedef T result_type;
template<typename InputIterator>
T operator()(InputIterator first, InputIterator last) const
{
if(first == last)
throw std::runtime_error("Cannot compute maximum of zero elements!");
return *std::max_element(first, last);
}
};
template<typename Container>
struct aggregate_values
{
typedef Container result_type;
template<typename InputIterator>
Container operator()(InputIterator first, InputIterator last) const
{
return Container(first, last);
}
};
int main()
{
fd::delegate2<int, int, int> dg_max;
dg_max += std::plus<int>();
dg_max += std::multiplies<int>();
dg_max += std::minus<int>();
dg_max += std::divides<int>();
std::cout << dg_max(5, 3, maximum<int>()) << std::endl;
std::vector<int> vec_result = dg_max(5, 3,
aggregate_values<std::vector<int> >());
assert(vec_result.size() == 4);
std::cout << vec_result[0] << std::endl;
std::cout << vec_result[1] << std::endl;
std::cout << vec_result[2] << std::endl;
std::cout << vec_result[3] << std::endl;
return 0;
}
Under the hood
Part A: storing a function pointer for later invocation without requiring heap memory allocation.
According to C++ standards, a function pointer -- both free function pointer and member function pointer -- cannot be converted or stored into a void *
. A function pointer may be converted into a function pointer of a different type signature, however, the result of such conversion cannot be used; it can only be converted back. The size of the member function varies over the different platforms from 4 bytes to 16 bytes. To avoid heap allocation to store the member function, some well-known template meta programming techniques have been adapted. These permit a member function pointer whose size is less than or equal to the size of the predefined generic member function pointer to be stored without heap memory allocation. The stored generic member function pointer is restored back to its original member function type before use.
typedef void generic_fxn();
class alignment_dummy_base1 { };
class alignment_dummy_base2 { };
class alignment_dummy_s : alignment_dummy_base1 { };
class alignment_dummy_m : alignment_dummy_base1, alignment_dummy_base2 { };
class alignment_dummy_v : virtual alignment_dummy_base1 { };
class alignment_dummy_u;
typedef void (alignment_dummy_s::*mfn_ptr_s)();
typedef void (alignment_dummy_m::*mfn_ptr_m)();
typedef void (alignment_dummy_v::*mfn_ptr_v)();
typedef void (alignment_dummy_u::*mfn_ptr_u)();
typedef void (alignment_dummy_m::*generic_mfn_ptr)();
union max_align_for_funtion_pointer
{
void const * dummy_vp;
generic_fxn * dummy_fp;
boost::ct_if<( sizeof( generic_mfn_ptr ) < sizeof( mfn_ptr_s ) ),
generic_mfn_ptr, mfn_ptr_s>::type dummy_mfp1;
boost::ct_if<( sizeof( generic_mfn_ptr ) < sizeof( mfn_ptr_m ) ),
generic_mfn_ptr, mfn_ptr_m>::type dummy_mfp2;
boost::ct_if<( sizeof( generic_mfn_ptr ) < sizeof( mfn_ptr_v ) ),
generic_mfn_ptr, mfn_ptr_v>::type dummy_mfp3;
boost::ct_if<( sizeof( generic_mfn_ptr ) < sizeof( mfn_ptr_u ) ),
generic_mfn_ptr, mfn_ptr_u>::type dummy_mfp4;
};
BOOST_STATIC_CONSTANT( unsigned,
any_fxn_size = sizeof( max_align_for_funtion_pointer ) );
union any_fxn_pointer
{
void const * obj_ptr;
generic_fxn * fxn_ptr;
generic_mfn_ptr mfn_ptr;
max_align_for_funtion_pointer m_;
};
A member function pointer whose size is less than or equal to any_fxn_size
is stored into any_fxn_pointer
. any_fxn_pointer
is implemented to make it able to store one out of three different pointer types -- a void
data pointer, a function pointer, or a member function pointer -- whose size is less than a function pointer to the member of a multiple inherited class. Only one pointer type is stored at one specific time. Care has been taken regarding the misalignment issue, which may cause undefined behavior according to the C++ standard, by applying the specialized version of the well-known max alignment union trickery.
void hello(int, float) { }
typedef void (*MyFxn)(int, float);
struct foobar
{
void foo(int, float) { }
};
typedef void (foobar::*MyMfn)(int, float);
void test1(any_fxn_pointer any)
{
( *reinterpret_cast<MyFxn>( any.fxn_ptr ) )( 1, 1.0f );
}
void test2(any_fxn_pointer any, foobar * pfb)
{
( pfb->*reinterpret_cast<MyMfn>( any.mfn_ptr ) )( 1, 1.0f );
}
void main()
{
any_fxn_pointer any;
any.fxn_ptr = reinterpret_cast<generic_fxn *>( &hello );
test1( any );
foobar fb;
any.mfn_ptr = reinterpret_cast<generic_mfn_ptr>( &foobar::foo );
test2( any, &fb );
}
When the size of a member function pointer is greater than any_fxn_size
, takes for an example when storing a member function pointer to virtual
inherited class in MSVC, it is stored by allocating heap memory in the same way that Boost.Function
does, as a non-fast delegate. However, in real world practices, virtual
inheritance is rarely used.
template<typename U, typename T>
void bind(UR (U::*fxn)(int, float), T t)
{
struct select_stub
{
typedef void (U::*TFxn)(int, float);
typedef typename boost::ct_if<( sizeof( TFxn ) <= any_fxn_size ),
typename impl_class::fast_mfn_delegate,
typename impl_class::normal_mfn_delegate
>::type type;
};
select_stub::type::bind( *this, fxn, t, );
}
Part B: using any_fxn_pointer
any_fxn_pointer is used for storing an arbitrary function pointer with the class template. This is done in order to erase the type while storing the function and to restore the original type safely when required later. Sergey Ryazanov demonstrated in his article that a C++ standard compliant fast delegate for member function can be implemented using the class template with a non-type member function template parameter. The sample below shows a rough idea of how it had been implemented.
class delegate
{
typedef void (*invoke_stub)(void const *, int);
void const * obj_ptr_;
invoke_stub stub_ptr_;
template<typename T, void (T::*Fxn)(int)>
struct mem_fn_stub
{
static void invoke(void const * obj_ptr, int a0)
{
T * obj = static_cast<T *>( const_cast<void *>( obj_ptr ) );
(obj->*Fxn)( a0 );
}
};
template<typename T, void (T::*Fxn)(int) const>
struct mem_fn_const_stub
{
static void invoke(void const * obj_ptr, int a0)
{
T const * obj = static_cast<T const *>( obj_ptr );
(obj->*Fxn)( a0 );
}
};
template<void (*Fxn)(int)>
struct function_stub
{
static void invoke(void const *, int a0)
{
(*Fxn)( a0 );
}
};
public:
delegate() : obj_ptr_( 0 ), stub_ptr_( 0 ) { }
template<typename T, void (T::*Fxn)(int)>
void from_function(T * obj)
{
obj_ptr_ = const_cast<T const *>( obj );
stub_ptr_ = &mem_fn_stub<T, Fxn>::invoke;
}
template<typename T, void (T::*Fxn)(int) const>
void from_function(T const * obj)
{
obj_ptr_ = obj;
stub_ptr_ = &mem_fn_const_stub<T, Fxn>::invoke;
}
template<void (*Fxn)(int)>
void from_function()
{
obj_ptr_ = 0;
stub_ptr_ = &function_stub<Fxn>::invoke;
}
void operator ()(int a0) const
{
( *stub_ptr_ )( obj_ptr_, a0 );
}
};
Even though passing a member function as a non-type template parameter is a legitimate C++ feature -- somewhat outdated, but still widely used -- compilers do not support it. The real problem with Sergey's implementation of using the member function as non-type template parameter is not the lack of support from those outdated compilers, but rather its awful syntax. This is due to the fact that non-type template parameters cannot participate in template argument deduction from the function arguments provided. Therefore it must be explicitly specified all the time.
struct foobar
{
void foo(int) { }
void bar(int) const { }
};
void hello(int) { }
void main()
{
foobar fb;
foobar * pfb = &fb;
delegate dg;
dg.from_function<foobar, &foobar::foo>( pfb );
dg( 1 );
dg.from_function<foobar const, &foobar::bar>( pfb );
dg( 1 );
dg.from_function<&hello>();
dg( 1 );
}
It is a really bad idea in practice that you should provide template arguments explicitly every time. Using any_fxn_pointer
introduced previously, we can significantly improve the syntax of the usage and, in turn, the degree of convenience. We just need to add an any_fxn_pointer
as a member of the delegate class to store the address of the function of interest.
class delegate
{
typedef void (*invoke_stub)(void const *, any_fxn_pointer, int);
void const * obj_ptr_;
any_fxn_pointer any_;
invoke_stub stub_ptr_;
template<typename T, typename TFxn>
struct mem_fn_stub
{
static void invoke(void const * obj_ptr, any_fxn_pointer any, int a0)
{
T * obj = static_cast<T *>( const_cast<void *>( obj_ptr ) );
(obj->*reinterpret_cast<TFxn>( any.mfn_ptr ) )( a0 );
}
};
template<typename TFxn>
struct function_stub
{
static void invoke(void const *, any_fxn_pointer any, int a0)
{
(*reinterpret_cast<TFxn>( any.fxn_ptr ) )( a0 );
}
};
public:
delegate() : obj_ptr_( 0 ), any(), stub_ptr_( 0 ) { }
template<typename T>
void from_function(void (T::*fxn)(int ), T * obj)
{
typedef void (T::*TFxn)(int);
obj_ptr_ = const_cast<T const *>( obj );
any_.mfn_ptr = reinterpret_cast<generic_mfn_ptr>( fxn );
stub_ptr_ = &mem_fn_stub<T, TFxn>::invoke;
}
template<typename T>
void from_function(void (T::*fxn)(int) const, T const * obj)
{
typedef void (T::*TFxn)(int) const;
obj_ptr_ = obj;
any_.mfn_ptr = reinterpret_cast<generic_mfn_ptr>( fxn );
stub_ptr_ = &mem_fn_stub<T const, TFxn>::invoke;
}
void from_function(void (*fxn)(int))
{
typedef void (*TFxn)(int);
obj_ptr_ = 0;
any_.fxn_ptr = reinterpret_cast<generic_fxn *>( fxn );
stub_ptr_ = &function_stub<TFxn>::invoke;
}
void operator ()(int a0) const
{
( *stub_ptr_ )( obj_ptr_, any_, a0 );
}
};
Not even this works for those outdated compilers that do not support member functions as non-type template parameters. It becomes more intuitive syntax and thus easier to use, since function overloading and automatic template argument deduction is now applicable.
struct foobar
{
void foo(int) { }
void bar(int) const { }
};
void hello(int) { }
void main()
{
foobar fb;
foobar * pfb = &fb;
delegate dg;
dg.from_function( &foobar::foo, pfb );
dg( 1 );
dg.from_function( &foobar::bar, pfb );
dg( 1 );
dg.from_funcion( &hello );
dg( 1 );
}
Part C: using function reference tables to support rich features
One of interesting features of Boost.Function
is its ability to store a member function along with the bound object in various forms: a) a pointer or a reference to the bound object of the type on which the member function call is made, or b) an instance of an arbitrary smart pointer to the bound object. As a matter of fact, it is precise to say that this feature is of Boost.Mem_fn
and Boost.Bind
. Boost.Function
has a 'manager
' member and defines enumerate tags of so-called 'functor_manager_operation_type
'.
enum functor_manager_operation_type
{
clone_functor_tag,
destroy_functor_tag,
check_functor_type_tag
};
It also defines several tags to distinguish among different types of functions.
struct function_ptr_tag {};
struct function_obj_tag {};
struct member_ptr_tag {};
struct function_obj_ref_tag {};
struct stateless_function_obj_tag {};
This is a traditional tag dispatching technique that uses function overloading to dispatch based on properties of type. This sort of tag dispatching works quite reasonably, but with slightly over complicated construct detail. This is because an implementation for a specific function type is usually scattered here and there. If you have ever tried to look into the Boost.Function
detail, you know what I mean. However, there exists one very neat and elegant alternative. It was initially introduced by Chris Diggings and Jonathan Turkanis in their series of articles about BIL (Boost.Interface.Libarary). We can start off by declaring a function reference table that contains a set of function pointers. These function pointers determine the behavioral requirement or concepts of the delegate that we want to design.
class delegate
{
typedef void generic_fxn();
struct fxn_table
{
void invoke(void const *, any_fxn_pointer, int);
void copy_obj(void cons **, void const *);
void delete_obj(void const *);
bool is_empty();
};
void const * obj_ptr_;
any_fxn_pointer any_;
fxn_table const * tbl_ptr_;
Then we define class templates that implement all of the entries of function reference table respectively.
template<typename T, typename TFxn>
struct mem_fn_stub
{
static void init(
void const ** obj_pptr, any_fxn_pointer & any, T * obj, TFxn fxn)
{
*obj_pptr = obj;
any.mfn_ptr = reinterpret_cast<generic_mfn_ptr>( fxn );
}
static void invoke(void const * obj_ptr, any_fxn_pointer any, int a0)
{
T * obj = static_cast<T *>( const_cast<void *>( obj_ptr ) );
(obj->*reinterpret_cast<TFxn>( any.mfn_ptr ) )( a0 );
}
static void copy_obj(void const ** obj_pptr_dest, void const * obj_ptr_src)
{
*obj_pptr_dest = obj_ptr_src;
}
static void delete_obj(void const *)
{
}
static bool is_empty()
{
return false;
}
static fxn_table const * get_table()
{
static fxn_table const static_table = { &invoke, ©_obj,
&delete_obj, &is_empty, };
return &static_table;
}
};
template<typename T, typename TFxn>
struct mem_fn_obj_stub
{
static void init(
void const ** obj_pptr, any_fxn_pointer & any, T * obj, TFxn fxn)
{
*obj_pptr = new T( *obj );
any.mfn_ptr = reinterpret_cast<generic_mfn_ptr>( fxn );
}
static void invoke(void const * obj_ptr, any_fxn_pointer any, int a0)
{
T * obj = static_cast<T *>( const_cast<void *>( obj_ptr ) );
( get_pointer(*obj)->*reinterpret_cast<TFxn>( any.mfn_ptr ) )( a0 );
}
static void copy_obj(void const ** obj_pptr_dest, void const * obj_ptr_src)
{
*obj_pptr_dest = new T( *static_cast<T const *>( obj_ptr_src ) );
}
static void delete_obj(void const * obj_ptr)
{
delete static_cast<T const *>( obj_ptr );
}
static bool is_empty()
{
return false;
}
static fxn_table const * get_table()
{
static fxn_table const static_table = { &invoke, ©_obj,
&delete_obj, &is_empty, };
return &static_table;
}
};
template<typename TFxn>
struct function_stub
{
static void init(void const ** obj_pptr, any_fxn_pointer & any, TFxn fxn)
{
*obj_pptr = 0;
any.fxn_ptr = reinterpret_cast<generic_fxn *>( fxn );
}
static void invoke(void const *, any_fxn_pointer any, int a0)
{
(*reinterpret_cast<TFxn>( any.fxn_ptr ) )( a0 );
}
static void copy_obj(void const ** obj_pptr_dest, void const * obj_ptr_src)
{
*obj_pptr_dest = obj_ptr_src;
}
static void delete_obj(void const *)
{
}
static bool is_empty()
{
return false;
}
static fxn_table const * get_table()
{
static fxn_table const static_table = { &invoke, ©_obj,
&delete_obj, &is_empty, };
return &static_table;
}
};
We have defined three class templates above. Actually, a class template is not necessary if no extra template parameter is required to implement the behavioral requirement of the specific function type. A normal class will be just fine in such case.
struct null_stub
{
static void invoke(void const *, any_fxn_pointer, int)
{
throw bad_function_call();
}
static void copy_obj(void const ** obj_pptr_dest, void const *)
{
*obj_pptr_dest = 0;
}
static void delete_obj(void const *)
{
}
static bool is_empty()
{
return true;
}
static fxn_table const * get_table()
{
static fxn_table const static_table = { &invoke, ©_obj,
&delete_obj, &is_empty, };
return &static_table;
}
};
We can now implement delegate class. We only use entries of the function reference table we declared in the first place through the pointer to the function reference table,
tbl_ptr_
, to implement member functions of a 'generic' delegate itself.
public:
delegate()
: obj_ptr_( 0 ), any_(), tbl_ptr_( null_stub::get_table() ) { }
delegate(delegate const & other)
: obj_ptr_( other.obj_ptr_ ), any_( other.any_ ),
tbl_ptr_( other.tbl_ptr_ )
{
if( other.tbl_ptr_ )
other.tbl_ptr_->copy_obj( &obj_ptr_, other.obj_ptr_ );
}
~delegate()
{
if( tbl_ptr_ )
tbl_ptr_->delete_obj( obj_ptr_ );
}
void swap(delegate & other)
{
std::swap( obj_ptr_, other.obj_ptr_ );
std::swap( any_, other.any_ );
std::swap( tbl_ptr_, other.tbl_ptr_ );
}
bool empty() const
{
return tbl_ptr_->is_empty();
}
delegate & operator =(delegate const & other)
{
if( this != &other )
delegate( other ).swap( *this );
return *this;
}
void reset()
{
delegate().swap( *this );
}
void operator ()(int a0) const
{
tbl_ptr->invoke( obj_ptr_, any_, a0 );
}
Defining a class to represent 'null' has several benefits over initializing the pointer to the function reference table,
tbl_ptr_
, to a zero value. It is not necessary to check nullness of delegate at runtime. If the pointer to the function reference table were to be assigned to zero to represent null delegate instead, the function call operator should have contained an if-clause to check the nullness. Most of all, it should also have had an exception statement to throw when the call had been made on the empty delegate, which seemed quite an unreasonable penalty.
Since nullness of delegate is determined at compile time by introducing 'null' class, null_stub
, we can improve the performance by not checking the nullness at runtime and by not having throw statement if it is not a 'null' delegate. Finally, we can complete our new delegate class by adding interface member functions to support storing from various function types.
template<typename T, typename U>
void from_function(void (U::*fxn)(int), T obj)
{
reset();
typedef void (U::*TFxn)(int);
struct select_stub
{
typedef typename boost::ct_if<
(::boost::is_pointer<T>::value),
mem_fn_stub<typename boost::remove_pointer<T>::type, TFxn>,
mem_fn_obj_stub<T, TFxn>
> type;
};
select_stub::type::init( &obj_ptr_, any_, obj, fxn );
tbl_ptr_ = select_stub::type::get_table();
}
template<typename T, typename U>
void from_function(void (U::*fxn)(int) const, T obj)
{
reset();
typedef void (U::*TFxn)(int) const;
struct select_stub
{
typedef typename boost::ct_if<
(::boost::is_pointer<T>::value),
mem_fn_stub<typename boost::remove_pointer<T>::type, TFxn>,
mem_fn_obj_stub<T, TFxn>
> type;
};
select_stub::type::init( &obj_ptr_, any_, obj, fxn );
tbl_ptr_ = select_stub::type::get_table();
}
void from_function(void (*fxn)(int))
{
reset();
typedef void (*TFxn)(int);
function_stub<TFxn>::init( &obj_ptr_, any_, fxn );
tbl_ptr_ = function_stub<TFxn>::get_table();
}
};
We just added smart pointer support into our delegate. We can now use any kind of smart pointer to bind the target object on which the member function call is made, as long as the get_pointer()
overload for the smart pointer is provided in the visible namespace scope.
struct foobar
{
void foo(int) { }
void bar(int) const { }
};
template<typename T>
T * get_pointer(boost::shared_ptr<T> const & t)
{
return t.get();
}
void main()
{
boost::shared_ptr<foobar> spfb(new foobar);
delegate dg;
dg.from_function( &foobar::foo, spfb );
dg( 1 );
}
As demonstrated above, it is quite simple to add new function type support into our delegate. First, it determines all the behavioral requirements according to the characteristic of the function type that we are interested in storing. Then it adds a class template and starts defining all the entries of function reference table for the new function type per requirements. Finally, it adds an interface function overload -- from_function()
member function in the example above -- to support the newly added function type.
If we want to add more behavioral requirements or concepts into the delegate, we can add new entries into the function reference table to fulfill the purpose. Of course, all of the existing class templates already defined for some other function types must implement newly added entries accordingly. Otherwise, it will assert a compile error. Be aware that even if these newly added entries might not apply for some of class templates already defined for certain function types, they still have to define empty entries, which may do nothing and thus possibly be optimized away by the compiler.
As easily seen from the example above, all entries in a function reference table are declared in a type neutral form. This means that it may be determined by the function call type signature, but nothing else. Also, we can see that void pointers are passed over as their arguments, and the specific type information is given as the template parameters of the class template that implements all of the entries of the function reference table. Those void pointers passed in and out come alive and restore themselves back to their original identities by the aid of casting to one of the template parameters of class template. However, it will assuredly erase the type information only during its storage life span and convert back to the right identity when required. This is the key point of how the different set of behavioral requirements of a delegate for a specific function type is implemented in the type-safe manner, but with type-neutral persistency.
Implementation details are to be defined in one location, in a class template for a specific function type, so it has better readability and manageability than the tag dispatching technique. It is like a 'strategy pattern' to make it possible to encapsulate the interchangeable algorithm of delegate for various function types. Actually, a function reference table is an interface and the technique can be thought of as a static typing or static binding version of the virtual table feature in C++.
Every individual class template that implements all of the entries of a function reference table according to their own requirements for the specific function type are orthogonal to each other. This means that we don't need to concern ourselves with how a function object is stored into the delegate while designing a class template to store a member function, and vice versa. So what does this mean? I can now implement and support a multicast delegate much easier in one single delegate class definition. There will be absolutely no degradation in runtime performance nor will there be a size increase of non-multicast delegate. This is because they -- that is, the multicast delegate and non-multicast delegate -- are treated as completely different beasts of the same name.
Therefore it becomes very important how robustly we declare entries of a function reference table to generalize the behavioral requirements of the generic delegate that we are interested in storing from the given function type, as well as how to categorize these function types based on their own unique traits and requirements.
Selecting a proper function reference table for the specific function type takes place in the implementation of the interface function overloads of delegate class. It usually involves template meta programming along with type traits to determine or to transform from a function type. Unfortunately, some type traits I used while implementing the selection structure in the interface function overloads only work in places based on the template partial specialization feature of C++ (boost::remove_pointer<>
for example). This is why FD.Delegate
does not work on some outdated compilers that have no support or broken support for this feature. If I were to support only a limited feature set, like Don's fastest delegate or Sergey's fast delegate, I could have supported many outdated compilers as well. However, I wanted it to be a 'drop-in' replacement for Boost.Function
, so this was not an option for me.
D. Categorizing function types.
There are three dominant callable entities in the C++ world.
- Free function
- Member function
- Function object
These basic callable entities are strained off more refined species depending on how their bound object or function object is stored and managed internally. See the table below.
Function type | Category | Description |
---|
|
FT01. | 1. | Free function (including static member function) |
---|
|
FT02. | 2-a. | Member function on a pointer to an object of the type that the member function belongs to. |
---|
FT03. | 2-b. | Member function on a reference to an object of the type that the member function belongs to. |
---|
FT04. | 2-c. | Member function on a copy of an object of either the type that the member function belongs to or any type that supports get_pointer() overload (i.e. smart pointers). |
---|
|
FT05. | 2-d. | Member function bound with a pointer or a reference to an object of the type that the member function belongs to. |
---|
FT06. | 2-e. | Member function bound with a copy of an object of any type that supports get_pointer() overload (i.e. smart pointers). |
---|
|
FT07. | 3-a. | A pointer or a reference to a function object. |
---|
FT08. | 3-b. | A copy of a function object. |
---|
FT09. | 3-c. | Stateless function object. |
---|
|
FT10. | | Empty delegate. |
---|
|
FT11. | | Multicast delegate. |
---|
In the previous delegate example, we added supports for function type FT01, FT05, FT06 and FT10. To make it easier to understand what these function types are, see the example as illustrated below.
struct foobar
{
int id_;
void foo(int) { }
static void bar(int) { }
void operator ()(int) const { }
};
void hello(int) { }
struct stateless
{
void operator ()(int) const { }
};
void main()
{
delegate<void (int)> dg1;
foobar fb;
foobar * pfb = &fb;
boost::shared_ptr<foobar> spfb( new foobar );
dg1 = &hello;
dg1( 1 );
dg1 = &foobar::bar;
dg1( 1 );
delegate<void (foobar *, int)> dg2;
dg2 = &foobar::foo;
dg2( pfb, 1 );
delegate<void (foobar &, int)> dg3;
dg3 = &foobar::foo;
dg3( fb, 1 );
delegate<void (foobar, int)> dg4;
dg4 = &foobar::foo;
dg4( fb, 1 );
delegate<void (boost::shared_ptr<foobar>, int)> dg5;
dg5 = &foobar::foo;
dg5( spfb, 1 );
dg1.bind( &foobar::foo, pfb );
dg1( 1 );
dg1.bind( &foobar::foo, boost::ref( fb ) );
dg1( 1 );
dg1.bind( &foobar::foo, spfb );
dg1( 1 );
dg1 = pfb;
dg1( 1 );
dg1 = boost::ref( fb );
dg1( 1 );
dg1 = fb;
dg1( 1 );
dg1 = stateless();
dg1( 1 );
dg1 = 0;
try { dg1( 1 ); }
catch(bad_function_call) { }
dg1 += &hello;
dg1 += delegate<void (int)>( &foobar::foo, spfb );
dg1 += fb;
dg1( 1 );
}
Part E: list of entries of function reference table
As emphasized previously, it is very important to declare common and representative entries of the function reference table for a generic delegate. It is the basis of the whole FD.Delegate
's design and determines performance, as well as robustness. The following list of function entries are declared in order to generalize the common behavioral requirements of a generic delegate for the various function types that we categorized above.
Invocation
- invoke() - Invokes the underlying callable entity.
Object management
- copy_obj() - Copies the object to destination from source.
- delete_obj() - Deletes the object.
General information inquiry
- is_empty() - Determines whether or not the delegate is empty.
- size_of_fxn() - Retrieves size of the underlying callable entity.
- type_of_fxn() - Retrieves std::type_info of the underlying callable entity.
- is_functor() - Determines whether or not the underlying callable entity is a function object.
Comparisons
- compare_equal_same_type() - Compares equality of two underlying callable entities of the same type.
- memcmp_delegate() - Non contextual memory comparison between underlying callable entities of two arbitrary types.
Multicast
- is_multicast() - Determines whether or not the delegate is a multicast.
- add_delegates() - Adds one or more delegates into multicast.
- remove_delegates() - Removes one or more delegates from multicast.
- find_delegate() - Determines whether or not the specified delegate is in multicast.
Part F: generic data structure to store a callable entity
It is required to have at least two void pointers and an any_fxn_pointer
to store all the previously categorized function types into a generic delegate. To make it easy to access and manipulate these void pointers, as well as any_fxn_pointer
, they are bundled up together into a single structure called 'delegate_holder
'.
struct delegate_holder
{
void const * obj_ptr;
any_fxn_pointer any;
void const * tbl_ptr;
};
See the table below to understand how these void pointer members are used effectively in a situation of storing one of the various function types.
Function type | delegate_holder | Fast delegate (Yes / No)
| Equality comparison with self type (compare_eq_same_type)
|
---|
.obj_ptr | .any | .tbl_ptr |
FT01 | Not used | Function Pointer | Pointer to function reference table | Yes | Compare any.fxn_ptr |
FT02 | Not used | Member function pointer | Pointer to function reference table | Yes | Compare any.mfn_ptr |
FT03 | Not used | Member function pointer | Pointer to function reference table | Yes | Compare any.mfn_ptr |
FT04 | Not used | Member function pointer | Pointer to function reference table | Yes | Compare any.mfn_ptr |
FT05 | Pointer to bound object | Member function pointer | Pointer to function reference table | Yes | Compare any.mfn_ptr, obj_ptr and *obj_ptr in order |
FT06 | Pointer to heap allocated bound object | Member function pointer | Pointer to function reference table | No | Compare any.mfn_ptr, get_pointer(*obj_ptr) and *get_pointer(*obj_ptr) in order |
FT07 | Pointer to function object | Not used | Pointer to function reference table | Yes | Compare obj_ptr and *obj_ptr in order |
FT08 | Pointer to heap allocated function object | Not used | Pointer to function reference table | No | Compare *obj_ptr |
FT09 | Not used | Not used | Pointer to function reference table | Yes | TRUE always |
FT10 | Not used | Not used | Pointer to function reference table | Yes | TRUE always |
FT11 | Pointer to heap allocated delegate_holder list | Not used | Pointer to function reference table | No | for_each (list of delegate_holder) compare_eq_same_type one by one |
Part G: supporting multicast
In order to support multicast, it is important to model the EqualityComparable concept to be able to compare between two delegates of the same type. As explained in the Frequently Asked Questions of Boost.Function
, it is a well-known fact that there is no reasonable method at the moment to distinguish whether or not the specific function object has an accessible equality comparison operator defined. Because of the Boost.Function
and FD.Delegate
erasing of type information and restoring it back when required, it will assert a compile error. This is regardless of whether the comparison between two delegates of an arbitrary function object type has actually been performed or not. This is also the case if it is forced to use the equality comparison operator when the equality comparison operator is not available or not accessible for the specified function object type.
Obviously, it is not acceptable to cause a compile error when we do not compare delegates. So, FD.Delegate
was made to return false
by default and it does not use the equality comparison operator when comparing between two delegates of the same type that store a function object as their underlying callable entity. However, it can be altered to return the result of the equality comparison operator of the specific function object type, thus possibly true
, if a certain condition is met. This will be explained later. By the way, there is no issue of comparing between two delegates of the same type when their underlying callable entity is either a free function or a member function, since function pointers of the same type are equality comparable in C++.
As mentioned previously, it was possible to compare between two delegates of the same function object type using the equality comparison operator, instead of returning false
blindly, if a certain condition is met. The user can manually indicate that the specific function object type has an accessible equality comparison operator by defining a template specialization of fd::is_equality_comparable<>
for the function object type or possibly by using the easier macro definition provided for this purpose, FD_DELEGATE_EQUALITY_COMPARABLE_TYPE
. FD.Delegate
will use the equality comparison operator and will return the result of the comparison according to the implementation of the operator.
struct make_int
{
make_int(int n, int cn) : N(n), CN(cn) {}
int operator()() { return N; }
int operator()() const { return CN; }
int N;
int CN;
};
bool operator ==(make_int const & lhs, make_int const & rhs)
{
return lhs.N == rhs.N;
}
FD_DELEGATE_EQUALITY_COMPARABLE_TYPE(make_int);
void main()
{
delegate0<void> dg1, dg2;
dg1 = make_int( 1, 10 );
dg2 = make_int( 1, 20 );
assert( dg1.equal_to( dg2 ) == true );
}
If line (A) is commented out, the assert statement (B) will always hold false
and never become true
, as explained previously. Also note that we do not use the equality comparison operator for FD.Delegate
to compare between the two. This is because it is not a valid expression in Boost.Function
for same reasons as explained above. Furthermore, FD.Delegate
is a Boost.Function
'drop-in' replacement. So instead, we use the equal_to()
member function for an equality comparison between two delegates. The following code snippet is a pseudo-code illustration of how comparison between two delegates is performed in order.
bool delegate::equal_to(delegate const & other)
{
if( this->type_of_fxn() != other.type_of_fxn() )
then return false
if( this->get_holder().any.mfn_ptr != other.get_holder().any.mfn_ptr )
then return false;
if( this->get_holder().obj_ptr == other.get_holder().obj_ptr )
then return true;
return this->compare_equal_same_type( this->get_holder(),
other.get_holder() );
}
compare_equal_same_type()
is one of entries of the function reference table explained previously. So, a class template for a different function type can implement it differently according to its own requirement. All class templates whose designated function type requires the equality comparison between two of the same function object type will implement such a comparison to return false
. Otherwise, fd::is_equality_comparable<>
specialization for the specified function object type is defined to indicate the availability of the equality comparison operator.
Unfortunately, even such limited support for the equality comparison is not applicable for comparing between two delegates of the same type for an anonymous function object that we frequently face in everyday STL programming. It is not impossible, but it is impractical to define fd::is_equality_comparable<>
specialization for an anonymous function object. Boost.Signals
solves this problem by providing the connect()
method to return an object called a 'connection
'. The 'connection
' object can be used either to disconnect the connection made or to check the validity of the connection. The same idea goes with FD.Delegate
, but with a slightly different name tag. FD.Delegate
has a member function called add()
, which is almost equivalent to operator +=
, but they are different in their return type.
operator +=
and operator -=
return a self-reference, while add()
returns an object called a 'token
' and remove()
returns the number of delegates in the multicast delegate. 'token
' is very similar to what is 'connection
' for Boost.Signals
. It can be used either to remove one or more delegates added before or to check the validity of the 'token
' itself.
void main()
{
delegate1<int, int> dg1;
dg1 += std::negate<int>();
token tk1 = dg1.add( std::bind1st( std::plus<int>(), 3 ) );
assert( tk1.valid() == true );
assert( dg1.count() == 2 );
dg1.remove( std::bind1st( std::plus<int>(), 3 ) );
assert( dg1.count() == 2 );
tk1.remove();
assert( dg1.count() == 1 );
}
Part H: combiner interface
What is missing in C# delegate is multiple return values management of multicast invocation. Boost.Signals
employes a technique, called combiner interface, to serve two purposes effectively: a) multiple return values management; b) multiple invocations control. See the Boost.Signals
documentation for the combiner interface.
The idea and most of the implementation details of Boost.Signals
's combiner interface are copied and incorporated into FD.Delegate
. However, there is one big difference in their usage as multicast delegates. In order to be coincident with Boost.Function
, the combiner interface of FD.Delegate
is not designed to be a part of the delegate class type as a form of template parameter the way it is for Boost.Signals
. Instead, FD.Delegate
introduces a special function call operator that takes an instance of the combiner interface as the last function call argument. See the example below.
struct maximum
{
typedef int result_type;
template<typename InputIterator>
int operator()(InputIterator first, InputIterator last) const
{
if(first == last)
return 0;
int max = *first++;
for(; first != last; ++first)
max = (*first > max)? *first : max;
return max;
}
};
void main()
{
delegate2<int, int, int> dg1;
dg1 += std::plus<int>();
dg1 += std::multiplies<int>();
dg1 += std::minus<int>();
dg1 += std::divides<int>();
int max = dg1( 5, 3, maximum() );
assert( max == 15 );
}
While a combiner interface is used to manage multiple return values of multicast invocation in most cases, it can be also used to control the multiple invocation itself via multicast_call_iterator
, which is copied and modified from Boost.Signals
's slot_call_iterator
. See the example below. It is possible to abort all invocations of delegates in the multicast or to skip certain invocations of delegates when the combiner interface provided is implemented to require such operational conditions.
template<typename T>
struct first_positive
{
typedef T result_type;
template<typename InputIterator>
T operator()(InputIterator first, InputIterator last) const
{
while (first != last && !(*first > 0))
{
++first;
}
return (first == last) ? 0 : *first;
}
};
template<typename T>
struct noisy_divide
{
typedef T result_type;
T operator()(T const & x, T const & y) const
{
std::cout << "Dividing " << x << " and " << y << std::endl;
return x/y;
}
};
int main()
{
fd::delegate2<int, int, int> dg_positive;
dg_positive += std::plus<int>();
dg_positive += std::multiplies<int>();
dg_positive += std::minus<int>();
dg_positive += noisy_divide<int>();
assert(dg_positive(3, -5, first_positive<int>()) == 8);
return 0;
}
Any combiner interface that is implemented for Boost.Signals
will work for FD.Delegate
without a modification. It is obvious that FD.Delegate
supports many useful features of Boost.Signals
when it operates as a multicast delegate. However, it is never meant to replace Boost.Signals
. Some very sophisticated features of Boost.Signals
are not implemented in FD.Delegate
, but it can still serve as more than a generic multicast delegate while it stands for a 'drop-in' replacement of Boost.Function
.
Delegates comparison chart
Feature Description | Don.FD | Sergey.FD | Jae.FD | Boost.Function | Boost.Signals |
---|
Free function (including static member function) / FT01 | Yes | Yes | Yes | Yes | Yes |
---|
Member function on a pointer to object of the type which the member function belongs to / FT02 | No | No | Yes | Yes | Yes |
---|
Member function on a reference to object of the type which the member function belongs to / FT03 | No | No | Yes | Yes | Yes |
---|
Member function on a copy of an object of either the type which the member function belongs to or any type which supports get_pointer() overload (i.e. smart pointers) / FT04 | No | No | Yes | Yes | Yes |
---|
Member function bound with a pointer to object of the type which the member fucntion belongs to / FT05 | Yes | Yes | Yes | Yes | Yes |
---|
Member function bound with a copy of an object of any type which supports get_pointer() overload (i.e. smart pointers) / FT06 | No | No | Yes | Yes | Yes |
---|
A pointer to a function object / FT07 | No | No | Yes | Yes | Yes |
---|
A copy of an object of function object type / FT08 | No | No | Yes | Yes | Yes |
---|
Stateless function object / FT09 | No | No | Yes | Yes | Yes |
---|
Empty delegate throw bad_function_call exception when invoked / FT10 | Possible | Possible | Yes | Yes | N/A |
---|
Multicast delegate / FT11 | No | No | Yes | No | Yes |
---|
Combiner interface to manage multiple returns of multicast | N/A | N/A | Yes | N/A | Yes |
---|
Combiner interface to control invocation of multicast ( xxx_call_iterator ) | N/A | N/A | Yes | N/A | Yes |
---|
Connection management (multicast) | N/A | N/A | Yes | N/A | Yes |
---|
Ordering slot call group (multicast) | N/A | N/A | No | N/A | Yes |
---|
Named slot (multicast) | N/A | N/A | No | N/A | Yes |
---|
Trackable support (multicast) | N/A | N/A | No | N/A | Yes |
---|
Relaxed function type signature | No | No | Yes | Yes | Yes |
---|
Fast Delegate for member function call | Yes | Yes | Yes | No | No |
---|
Size of object (32 bit system) | 8 or 12 bytes | 8 bytes | 16 bytes + α | 12 bytes + α | 36 bytes + α |
---|
Size of object when member function is stored (32 bit system) | 8 or 12 bytes | 8 bytes | 16 bytes | 12 bytes + α | 36 bytes + α |
---|
Copy speed when member function is stored | ●●●●● | ●●●●● | ●●●●○ | ●○○○○ | ●○○○○ |
---|
Invocation speed | ●●●●● | ●●●●○ | ●●●●○ | ●●●○○ | ●●●○○ |
---|
Equality comparable for self type | Yes | No | Support | No | No |
---|
Equality comparable to an arbitrary function type (not self type) | No | No | Yes | Yes | No |
---|
Less than comparable | Yes | No | Yes | No | No |
---|
Custom allocator | N/A | N/A | Yes | Yes | Yes |
---|
Calling conventions ( __stdcall, __fastcall, __cdecl ) | No | No | Yes | Yes | Yes |
---|
Boost.Function drop-in replacement | No | No | Yes | Yes | No |
---|
C++ standard compliant | No | Yes | Yes | Yes | Yes |
---|
Portability | ●●●●● | ●●○○○ | ●●●●○ | ●●●●● | ●●●●● |
---|
Boost Dependency | No | No | Yes | Yes | Yes |
---|
Helper function template and class template
FD.Bind
FD.Bind
is a set of helper function overloads that return an instance of FD.Delegate
. Unlike Boost.Bind
, FD.Bind
can only be used to bind the object on which the member function call is made with the member function. FD.Bind
can return a FD.Delegate
instance of either function type FT05 or FT06, where FT05 is a fast delegate function type. Of course, FD.Delegate
will work with Boost.Bind
flawlessly as well.
#include <fd/delegate.hpp>
#include <fd/delegate/bind.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/ref.hpp>
struct foobar
{
void foo(int) { }
};
void main()
{
fd::delegate<void (int)> dg1;
foobar fb;
foobar * pfb = &fb;
boost::shared_ptr<foobar> spfb( new foobar );
dg1.bind( &foobar::foo, pfb );
dg1 = fd::bind( &foobar::foo, pfb );
dg1( 1 );
dg1.bind( &foobar::foo, boost::ref( fb ) );
dg1 = fd::bind( &foobar::foo, boost::ref( fb ) );
dg1( 1 );
dg1.bind( &foobar::foo, fb );
dg1 = fd::bind( &foobar::foo, fb );
dg1( 1 );
dg1.bind( &foobar::foo, spfb );
dg1 = fd::bind( &foobar::foo, spfb );
dg1( 1 );
}
FD.Resolution
When support for the relaxed function type signature is implemented, the library become more useful. However, one issue has emerged as the price of convenience. See the example below.
struct foobar
{
long hello(long) { return 0; }
int foo(int) { return 0; }
long foo(long) { return 0; }
int bar(int) { return 0; }
int bar(long) { return 0; }
};
void main()
{
boost::function<int (foobar *, int)> fn;
fn = &foobar::hello;
fn = &foobar::foo;
my_delegate_do_not_support_relaxed<int (foobar *, int)> md;
md = &foobar::hello;
md = &foobar::foo;
}
Many libraries from Boost
as well as FD.Delegate
suffer from the ambiguity issue illustrated above. They usually support minimal but incomplete or inconvenient devices to remedy the issue. You can explicitly specify the return or argument type to help overload resolution by designating those types in the function call syntax.
void main()
{
foobar fb;
foobar * pfb = &fb;
boost::bind<int>( &foobar::foo, pfb, _1 );
boost::bind<long>( &foobar::foo, pfb, _1 );
?>( &foobar::bar, pfb, _1 );
// Can't solve the ambiguity.
boost::mem_fn<int>( &foobar::foo ); // (A)
boost::mem_fn<long>( &foobar::foo ); // (B)
// boost::mem_fn<int, ???>( &foobar::bar ); // Can't solve the ambiguity.
boost::function<int (int)> fn;
fd::bind<int>( &foobar::foo, pfb ); // (A)
fd::bind<long>( &foobar::foo, pfb ); // (B)
fd::bind<int, int>( &foobar::bar, pfb ); // (C)
fd::bind<int, long>( &foobar::bar, pfb ); // (D)
fd::delegate<int (int)> dg;
dg = fd::bind<int, int>( &foobar::bar, pfb ); // (C)
dg = fd::bind<int, long>( &foobar::bar, pfb ); // (D)
dg.bind<int, int>( &foobar::bar, pfb ); // (C)
dg.bind<int, long>( &foobar::bar, pfb ); // (D)
}
FD.Resolution
is a tiny utility class template that comes in handy in such situations to resolve overload resolution ambiguity. It is much more generic and intuitive because you don't need to look into the individual library details to figure out the order of the template parameters in order to specify the argument types in the right placement. Notice that Boost.Bind
and Boost.Mem_fn
only allow you to specify the return type to help function overload resolution. Also note that FD.Resolution
is an independent utility class and thus can be used without FD.Delegate
.
#include <fd/resolution.hpp>
using fd::resolution;
void main()
{
foobar fb;
foobar * pfb = &fb;
boost::bind( resolution<int (int)>::select( &foobar::foo ), pfb, _1 );
boost::bind( resolution<long (long)>::select( &foobar::foo ), pfb, _1 );
boost::bind( resolution<int (int)>::select( &foobar::bar ), pfb, _1 );
boost::bind( resolution<int (long)>::select( &foobar::bar ), pfb, _1 );
boost::mem_fn( resolution<int (int)>::select( &foobar::foo ) );
boost::mem_fn( resolution<long (long)>::select( &foobar::foo ) );
boost::mem_fn( resolution<int (int)>::select( &foobar::bar ) );
boost::mem_fn( resolution<int (long)>::select( &foobar::bar ) );
boost::function<int (int)> fn;
fn = boost::bind( resolution<int (int)>::select( &foobar::bar ), pfb, _1 );
fn = boost::bind( resolution<int (long)>::select( &foobar::bar ), pfb, _1 );
fd::bind( resolution<int (int)>::select( &foobar::foo ), pfb );
fd::bind( resolution<long (long)>::select( &foobar::foo ), pfb );
fd::bind( resolution<int (int)>::select( &foobar::bar ), pfb );
fd::bind( resolution<int (long)>::select( &foobar::bar ), pfb );
fd::delegate<int (int)> dg;
dg = fd::bind( resolution<int (int)>::select( &foobar::bar ), pfb );
dg = fd::bind( resolution<int (long)>::select( &foobar::bar ), pfb );
dg.bind( resolution<int (int)>::select( &foobar::bar ), pfb );
dg.bind( resolution<int (long)>::select( &foobar::bar ), pfb );
}
Regression test
See the '%FD_ROOT%/libs/delegate/test' for details. FD.Delegate
has been built based on Boost 1.33.1
. As mentioned, FD.Delegate
uses several type trait classes from Boost
to transform type information. These features, especially remove_xxx
s, require compiler support for the partial template specialization. VC6 and VC7 do not support or have broken support for the partial template specialization. However, there are some.
Test Name | Test Type | Intel C++ 8.1 WIN32 | Intel C++ 9.1 WIN32 | MinGW 3.4.2 | GNU gcc 3.4.43 | MSVC++ 6sp5 | MSVC++ 6sp5 #2 (3) | MSVC++ 2003sp1 | MSVC++ 2005sp1b |
---|
bind_cdecl _mf_test | run | Fail (1) | Fail (1) | Fail (2) | Fail (2) | Fail | Pass | Pass | Pass |
---|
bind_eq _test | run | Pass | Pass | Pass | Pass | Fail | Pass | Pass | Pass |
---|
bind _fastcall _mf_test | run | Pass | Pass | Fail (2) | Fail (2) | Fail | Pass | Pass | Pass |
---|
bind _function _test | run | Pass | Pass | Pass | Pass | Pass | Pass | Pass | Pass |
---|
bind _stdcall _mf_test | run | Pass | Pass | Fail (2) | Fail (2) | Fail | Pass | Pass | Pass |
---|
mem_fn _cdecl_test | run | Fail (1) | Fail (1) | Fail (2) | Fail (2) | Pass | Pass | Pass | Pass |
---|
mem_fn _derived _test | run | Pass | Pass | Pass | Pass | Pass | Pass | Pass | Pass |
---|
mem_fn _eq_test | run | Pass | Pass | Pass | Pass | Pass | Pass | Pass | Pass |
---|
mem_fn _fastcall _test | run | Pass | Pass | Fail (2) | Fail (2) | Pass | Pass | Pass | Pass |
---|
mem_fn _stdcall _test | run | Pass | Pass | Fail (2) | Fail (2) | Pass | Pass | Pass | Pass |
---|
mem_fn _test | run | Pass | Pass | Pass | Pass | Pass | Pass | Pass | Pass |
---|
mem_fn _void_test | run | Pass | Pass | Pass | Pass | Pass | Pass | Pass | Pass |
---|
empty _delegate | run | Pass | Pass | Pass | Pass | Pass | Pass | Pass | Pass |
---|
allocator _test | run | Pass | Pass | Pass | Pass | Fail | Fail | Pass | Pass |
---|
contains2 _test | run | Pass | Pass | Pass | Pass | Pass | Pass | Pass | Pass |
---|
contains _test | run | Pass | Pass | Pass | Pass | Fail | Fail | Pass | Pass |
---|
delegate _30 | compile | Pass | Pass | Pass | Pass | Pass | Pass | Pass | Pass |
---|
delegate _arith _cxx98 | run | Pass | Pass | Pass | Pass | Fail | Fail | Pass | Pass |
---|
delegate _arith _portable | run | Pass | Pass | Pass | Pass | Pass | Pass | Pass | Pass |
---|
delegate _n_test | run | Pass | Pass | Pass | Pass | Pass | Pass | Pass | Pass |
---|
delegate _ref _cxx98 | run | Pass | Pass | Pass | Pass | Fail | Fail | Pass | Pass |
---|
delegate _ref _portable | run | Pass | Pass | Pass | Pass | Pass | Pass | Pass | Pass |
---|
delegate _test | run | Pass | Pass | Pass | Pass | Pass | Pass | Pass | Pass |
---|
delegate _test _fail1 | compile _fail | Pass | Pass | Pass | Pass | Pass | Pass | Pass | Pass |
---|
delegate _test _fail2 | compile _fail | Pass | Pass | Pass | Pass | Pass | Pass | Pass | Pass |
---|
lambda _test | run | Pass | Pass | Pass | Pass | Fail | Fail | Pass | Pass |
---|
mem_fun _cxx98 | run | Pass | Pass | Pass | Pass | Fail | Fail | Pass | Pass |
---|
mem_fun _portable | run | Pass | Pass | Pass | Pass | Pass | Pass | Pass | Pass |
---|
stateless _test | run | Pass | Pass | Pass | Pass | Pass | Pass | Pass | Pass |
---|
std_bind _cxx98 | run | Pass | Pass | Pass | Pass | Fail | Fail | Pass | Pass |
---|
std_bind _portable | run | Pass | Pass | Pass | Pass | Pass | Pass | Pass | Pass |
---|
sum_avg _cxx98 | run | Pass | Pass | Pass | Pass | Fail | Fail | Pass | Pass |
---|
sum_avg _portable | run | Pass | Pass | Pass | Pass | Pass | Pass | Pass | Pass |
---|
function _type | run | Pass | Pass | Pass | Pass | Fail | Pass | Pass | Pass |
---|
get _pointer | run | Pass | Pass | Pass | Pass | Pass | Pass | Pass | Pass |
---|
multicast _and _empty | run | Pass | Pass | Pass | Pass | Fail | Pass | Pass | Pass |
---|
multicast _call _iterator | run | Pass | Pass | Pass | Pass | Pass | Pass | Pass | Pass |
---|
multiple _inheritance | run | Pass | Pass | Pass | Pass | Pass | Pass | Pass | Pass |
---|
resolution _select _cxx98 | run | Pass | Pass | Pass | Pass | Fail | Fail | Pass | Pass |
---|
resolution _select _portable | run | Pass | Pass | Pass | Pass | Fail | Fail | Pass | Pass |
---|
deletion _test | run | Pass | Pass | Pass | Pass | Pass | Pass | Pass | Pass |
---|
signal _n_test | run | Pass | Pass | Pass | Pass | Pass | Pass | Pass | Pass |
---|
signal_test | run | Pass | Pass | Pass | Pass | Fail | Fail | Pass | Pass |
---|
type_info | run | Pass | Pass | Pass | Pass | Fail | Pass | Fail (4) | Fail (4) |
---|
virtual _inheritance | run | Pass | Pass | Pass | Pass | Pass | Pass | Pass | Pass |
---|
(1) Non-specified calling convention is treated exactly the same as __cdecl
in Intel C++ compiler for WIN32. Therefore do not define both (non-specified and __cdecl
) at the same time.
References
- [Boost].
Boost.Function
, Boost.Bind
, Boost.Mem_fn
and Boost.Signals
- [Sergey.FD]. "The Impossibly Fast C++ Delegates" by Sergey Ryazanov
- [Don.FD]. "Member Function Pointers and the Fastest Possible C++ Delegates" by Don Clugston.
- [BIL]. "C++ Boost Interface Library ( BIL )" by by Jonathan Turkanis and Christopher Diggins.
History
- April 12 2007 - v1.00
- April 13 2007 - v1.01
- ARTICLE:
Boost.Function
's any_pointer
does not use the union trickery for the same reason. - ARTICLE: Uses a generic function pointer,
generic_fxn *
, to mark functions instead of void *
. - CODE: Used the pre-defined (
typedef void generic_fxn();
) generic function pointer to mark functions intead of void *
. This change has been made for target()
member function.
- April 18 2007 - v1.02
- ARTICLE: Assigning an arbitrary smart pointer of function object type (FT08) is not supported anymore.
- ARTICLE: Added regression test results for VC6 with
Boost 1.33.1
and Boost 1.34 alpha
. - CODE: Changed the way to apply
get_pointer()
to help VC6 for the overload resolution. Unfortunately, as a result, FT08 smart pointer type can not be used anymore. - CODE: Incorporated and modified
Boost
's get_function_tag<>
to help VC6 for the overload resolution. - CODE: Added Several workarouns for VC6 specific.
- CODE: Revised several test samples into portable syntax to make them work for VC6.
- April 30 2007 - v1.10
- ARTICLE: Incorrect information regarding to the union trickery has been completely removed.
- ARTICLE: Added explanation about the new approach to store member function pointer depending on its size.
any_fxn_pointer
. - CODE: Removed incorrect union trickery that was used for marking member function pointer.
- CODE: Added
any_fxn_pointer
implementation to substitute the removed union trickery. - CODE: Added two test cases to check storing member function pointer to multiple inherited class and
virtual
inherited class. - CODE: Added
simplify_mfn
struct simliar to Don's SimplifyMemFunc
to help MSVC which can not cast a member function pointer of unrelated classes even though it is required according to C++ standards.
- June 1, 2007 - Article edited and moved to the main CodeProject.com article base
(2) gcc
supports calling conventions, but does not effectively resolve the overload ambiguity among them.
(3) FD.Delegate
has been built based on remove_xxx
type traits from Boost 1.34 pre alpha
instead of Boost 1.33.1
since the new version incorporates workarounds for VC6, VC7 and VC7.1 specific.
(4) VC71 & VC8 bug.
unds for VC6, VC7 and VC7.1 specific.
(4) VC71 & VC8 bug.
References
- [Boost].
Boost.Function
, Boost.Bind
, Boost.Mem_fn
and Boost.Signals
- [Sergey.FD]. "The Impossibly Fast C++ Delegates" by Sergey Ryazanov
- [Don.FD]. "Member Function Pointers and the Fastest Possible C++ Delegates" by Don Clugston.
- [BIL]. "C++ Boost Interface Library ( BIL )" by by Jonathan Turkanis and Christopher Diggins.
History
- April 12 2007 - v1.00
- April 13 2007 - v1.01
- ARTICLE:
Boost.Function
's any_pointer
does not use the union trickery for the same reason. - ARTICLE: Uses a generic function pointer,
generic_fxn *
, to mark functions instead of void *
. - CODE: Used the pre-defined (
typedef void generic_fxn();
) generic function pointer to mark functions intead of void *
. This change has been made for target()
member function.
- April 18 2007 - v1.02
- ARTICLE: Assigning an arbitrary smart pointer of function object type (FT08) is not supported anymore.
- ARTICLE: Added regression test results for VC6 with
Boost 1.33.1
and Boost 1.34 alpha
. - CODE: Changed the way to apply
get_pointer()
to help VC6 for the overload resolution. Unfortunately, as a result, FT08 smart pointer type can not be used anymore. - CODE: Incorporated and modified
Boost
's get_function_tag<>
to help VC6 for the overload resolution. - CODE: Added Several workarouns for VC6 specific.
- CODE: Revised several test samples into portable syntax to make them work for VC6.
- April 30 2007 - v1.10
- ARTICLE: Incorrect information regarding to the union trickery has been completely removed.
- ARTICLE: Added explanation about the new approach to store member function pointer depending on its size.
any_fxn_pointer
. - CODE: Removed incorrect union trickery that was used for marking member function pointer.
- CODE: Added
any_fxn_pointer
implementation to substitute the removed union trickery. - CODE: Added two test cases to check storing member function pointer to multiple inherited class and
virtual
inherited class. - CODE: Added
simplify_mfn
struct simliar to Don's SimplifyMemFunc
to help MSVC which can not cast a member function pointer of unrelated classes even though it is required according to C++ standards.
- June 1, 2007 - Article edited and moved to the main CodeProject.com article base