As part of a policy-based particle engine I'm currently writting, I have done some type-erasure on policy classes. Specifically, I have done type erasure on the particle evoution policies:
A particle evolution policy is just a policy which says how the data (Coordinates, speed, color, etc) of a particle changes along the simulation. This is its interface:
struct evolution_policy
{
template<PARTICLE_DATA>
void operator()( PARTICLE_DATA& data );
void step();
};
The operator()
overload just takes a particle data and modifies it acording to the supposed policy. The step()
function is just a function to update (Advance) the state of the policy (If the policy has some state).
Among other cases, I just needed type erasure to allow the user to use simple function entities (Functions, lambdas, etc) to be used as a evolution policy, for example:
add_policy( []( some_particle_data_type& data ) { /* do something with */ } );
Where the add_policy()
function takes some policy and stores it in a vector. As you can see, the point of type erasure is to treat different kinds of policy classes/entities in the same homogeneous way.
I'm using the dynamic dispatch approach for type erasure:
template<tyoename PARTICLE_DATA>
struct type_erased_policy
{
public:
void operator()( PARTICLE_DATA& data )
{
(*_policy)( data );
}
void step()
{
_policy->step();
}
private:
struct policy_interface
{
virtual ~policy_interface() {}
virtual void operator()( PARTICLE_DATA& data ) = 0;
virtual void step() = 0;
};
template<typename POLICY>
class policy_impl : public policy_interface
{
public:
void operator()( PARTICLE_DATA& data ) override
{
_policy( particle );
}
void step() override
{
_policy.step();
}
private:
POLICY _policy;
};
std::shared_ptr<policy_interface> _policy;
};
With that in mind, its easy to write an specialization to type-erase shared pointers of policies, for example:
template<typename T>
class policy_impl<std::shared_ptr<T>> : public policy_interface
{
public:
void operator()( PARTICLE_DATA& data ) override
{
(*_policy)( data );
}
void step( cpp::evolution_policy_step step_type ) override
{
_policy->step( step_type );
}
private:
std::shared_ptr<T> _policy;
};
That could be useful if we need to share a policy between particles, for example.
We could write an specialization for std::function<void(PARTICLE_DATA&)>
policies using that pattern. However, this only works for explicit instantations of std::function<void(PARTICLE_DATA&)>
, ie if we pass a lambda function to the type_erased_policy
ctor, it will instantiate the generic policy_impl
case, because there is no specialization for the lambda type.
As lambda functors (closures) are unique (In other words, the type of a lambda expression is unique and unespecified) there is no easy way to do this kind of type erasure over a lambda .
My question is: My goal is to take any function entity (A lambda, a functor, a function pointer, a std::function
) and do type erasure on it in the way explained above. Is there any (other) way to match lambdas and/or other function entities to do type-erasure on them?
I'm using GCC 4.8.2
First, a policy class that detects .step()
:
namespace impl{
// eat a type and do nothing with it:
template<typename T> struct type_sink { typedef void type; };
template<typename T> using TypeSink = typename type_sink<T>::type;
// detect .step on T (T& means rvalue, T means lvalue, and T const& means const lvalue, etc)
template<typename T, typename=void>
struct has_step:std::false_type {};
template<typename T>
struct has_step<T, TypeSink< decltype( std::declval<T>().step() ) > >:
std::true_type
{};
}
template<typename T> struct has_step : impl::has_step<T> {};
So now we have a has_step<T>
traits class that is true_type
if T
has a .step()
method callable, and false_type
otherwise.
If we are feeding this to a function, we can pick which implementation to run with tag dispatching:
template<typename T>
return_type_whatever type_erase_helper( T&& t, std::true_type /* test passed */ ) {
// branch 1
}
template<typename T>
return_type_whatever type_erase_helper( T&& t, std::false_type /* test failed */ ) {
// branch 2
}
template<typename T>
return_type_whatever type_erase( T&& t ) {
return type_erase_helper( std::forward<T>(t), has_step< typename std::decay<T>::type& >() );
}
If you really want to specialize a particular class based on having step
or not, you can use SFINAE techniques. But type erasure does not rely on the type erasure implementation being based off of specialization: I'd just use tag dispatching on the function that generates the type erasure implementation object.
We can daisy-chain such tag dispatching. Another good one might be is_signature_compatible< T, R(Args...) >
:
namespace impl {
template<typename T, typename Sig,typename=void>
struct is_signature_compatible:std::false_type {};
template<typename T, typename R, typename... Args>
struct is_signature_compatible< T, R(Args...), typename std::enable_if<
std::is_convertible< typename std::result_of< T(Args...) >::type, R >::value
>::type >:std::true_type {};
// dunno if this is needed? Possibly, and shouldn't hurt:
template<typename T, typename... Args>
struct is_signature_compatible< T, void(Args...),
TypeSink< typename std::result_of< T(Args...) >::type >
>:std::true_type {};
}
template<typename T, typename Sig>
struct is_signature_compatible:impl::is_signature_compatible<T,Sig> {};
which we can then use to split the wheat from the chaff in a less "error occurs 10 recursive template
invocations deep" way.
To facilitate the technique wrap your lambda function in a std::function
. Since its constructor is greedy, it will absorbe any callable, yet the gain of wrapping would be declaring an explicit type
auto f1 = [](double a, double b)->int {
return a + b > 5.; // example function body
});
std::function<int(double,double)> f2 ( [](double a, double b)->int {
return a + b > 5.; // example function body
};);
So in the above context f1
faces the problems you mention, while f2
is a lambda (preserves all the convinience in declaring and using it) that, in a sense, has no 'stray type'
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.