Стирание типа и лямбда-выражения: (Частичное) соответствие специализации шаблона лямбда-выражениям

Прежде всего, некоторый контекст

Как часть движка частиц на основе политик, который я сейчас пишу, я произвел некоторое стирание типов в классах политик. В частности, я произвел стирание типа в правилах эвакуации частиц:

Политика эволюции частиц — это просто политика, которая говорит о том, как данные (координаты, скорость, цвет и т. Д.) Частицы изменяются в процессе симуляции. Это его интерфейс:

struct evolution_policy
{
template<PARTICLE_DATA>
void operator()( PARTICLE_DATA& data );

void step();
};

operator() Перегрузка просто берет данные частицы и изменяет их в соответствии с предполагаемой политикой. step() Функция — это просто функция для обновления (Advance) состояния политики (если у политики есть какое-либо состояние).

В других случаях мне просто нужно было стереть тип, чтобы пользователь мог использовать простые объекты функций (функции, лямбды и т. Д.) Для использования в качестве политики эволюции, например:

add_policy( []( some_particle_data_type& data ) { /* do something with */ } );

Где add_policy() Функция принимает некоторую политику и сохраняет ее в векторе. Как вы можете видеть, смысл стирания типа состоит в том, чтобы обрабатывать различные типы классов / объектов политики одним и тем же однородным способом.

Эта проблема:

Я использую подход динамической отправки для стирания типа:

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;
};

Имея это в виду, легко написать специализацию для стирания общих указателей политик, например:

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;
};

Это может быть полезно, например, если нам нужно разделить политику между частицами.

Мы могли бы написать специализацию для std::function<void(PARTICLE_DATA&)> политики, использующие этот шаблон. Тем не менее, это работает только для явные экземпляры std::function<void(PARTICLE_DATA&)>, т.е. если мы передадим лямбда-функцию type_erased_policy Ctor, он будет создавать экземпляр общего policy_impl дело, потому что нет специализации для лямбда-типа.

Поскольку лямбда-функторы (замыкания) уникальны (другими словами, тип лямбда-выражения уникален и не определен) нет простого способа сделать такое стирание типа по лямбде.

Мой вопрос: моя цель состоит в том, чтобы взять любой объект функции (лямбда, функтор, указатель функции, std::function) и выполните стирание типа, как описано выше. Есть ли какой-либо (другой) способ сопоставления лямбда-выражений и / или других функциональных объектов для удаления типов из них?

Платформа:

Я использую GCC 4.8.2

1

Решение

Во-первых, класс политики, который обнаруживает .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> {};

Итак, теперь у нас есть has_step<T> класс черт, который true_type если T имеет .step() вызываемый метод, и false_type иначе.

Если мы передаем это функции, мы можем выбрать, какую реализацию запустить с диспетчеризацией тегов:

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& >() );
}

Если вы действительно хотите специализировать определенный класс на основе наличия step или нет, вы можете использовать методы SFINAE. Но стирание типов не зависит от реализации стирания типов, основанной на специализации: я бы просто использовал диспетчеризацию тегов для функции, которая генерирует объект реализации стирания типов.

Мы можем организовать последовательную передачу таких тегов. Еще один хороший может быть 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> {};

который мы можем затем использовать, чтобы отделить пшеницу от мякины в менее «ошибка возникает 10 рекурсивный template заклинания глубокого «пути».

3

Другие решения

Для облегчения техника оберните вашу лямбда-функцию в std::function, Так как его конструктор жадный, он будет поглощать любое вызываемое, но выигрыш от оборачивания будет объявлять явный тип

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
};);

Так что в приведенном выше контексте f1 сталкивается с проблемами, которые вы упоминаете, в то время как f2 это лямбда (сохраняет все удобство в объявлении и использовании), что, в некотором смысле, не имеет «случайного типа»

2

По вопросам рекламы [email protected]