Статические функции из boost.lambda или boost.phoenix
Я регулярно использую boost.lambda (и phoenix) для определения лямбда-функций в C++. Мне очень нравится их полиморфное свойство, простота их представления и то, как они делают функциональное программирование на C++ намного проще. В некоторых случаях даже чище и удобочитаемее (если вы привыкли их читать) использовать их для определения небольших функций и именования их в статической области видимости.
Способ хранения этих функционалов, который больше всего напоминает обычные функции, состоит в том, чтобы захватить их в boost::function
const boost::function<double(double,double)> add = _1+_2;
Но проблема заключается в неэффективности выполнения этого. Хотя add
функция здесь не имеет состояния, возвращаемый лямбда-тип не пустой и его sizeof
больше 1 (так boost::function
ctor по умолчанию и ctor копирования будут включать new
). Я действительно сомневаюсь, что есть механизм со стороны компилятора или поддержки, чтобы обнаружить это отсутствие состояния и генерировать код, который эквивалентен использованию:
double (* const add)(double,double) = _1+_2; //not valid right now
Можно, конечно, использовать C++ 11 auto
, но тогда переменная не может быть передана без шаблонных контекстов. Мне наконец удалось сделать почти то, что я хочу, используя следующий подход:
#include <boost/lambda/lambda.hpp>
using namespace boost::lambda;
#include <boost/type_traits.hpp>
#include <boost/utility/result_of.hpp>
using namespace boost;
template <class T>
struct static_lambda {
static const T* const t;
// Define a static function that calls the functional t
template <class arg1type, class arg2type>
static typename result_of<T(arg1type,arg2type)>::type
apply(arg1type arg1,arg2type arg2){
return (*t)(arg1,arg2);
}
// The conversion operator
template<class func_type>
operator func_type*() {
typedef typename function_traits<func_type>::arg1_type arg1type;
typedef typename function_traits<func_type>::arg2_type arg2type;
return &static_lambda<T>::apply<arg1type,arg2type>;
}
};
template <class T>
const T* const static_lambda<T>::t = 0;
template <class T>
static_lambda<T> make_static(T t) {return static_lambda<T>();}
#include <iostream>
#include <cstdio>
int main() {
int c=5;
int (*add) (int,int) = make_static(_1+_2);
// We can even define arrays with the following syntax
double (*const func_array[])(double,double) = {make_static(_1+_2),make_static(_1*_2*ref(c))};
std::cout<<func_array[0](10,15)<<"\n";
std::fflush(stdout);
std::cout<<func_array[1](10,15); // should cause segmentation fault since func_array[1] has state
}
Скомпилировано с gcc 4.6.1. Вывод этой программы (независимо от уровня оптимизации):
25
Segmentation fault
как и ожидалось. Здесь я сохраняю статический указатель на тип лямбда-выражения (максимально возможный для целей оптимизации) и инициализирую его как NULL
, Таким образом, если вы попытаетесь "статизировать" лямбда-выражение с помощью state, вы обязательно получите ошибку времени выполнения. И если вы статизируете действительно лямбда-выражение без сохранения состояния, все сработает.
На вопрос (ы):
Метод кажется немного грязным, можете ли вы вспомнить какие-либо обстоятельства или допущение компилятора, которое приведет к такому поведению (ожидаемое поведение: работает нормально, если лямбда не имеет состояния, в противном случае - segfault).
Можете ли вы придумать, каким образом попытка этого приведет к ошибке компилятора, а не к segfault, когда лямбда-выражение имеет состояние?
РЕДАКТИРОВАТЬ после ответа Эрика Ниблера:
#include <boost/phoenix.hpp>
using namespace boost::phoenix;
using namespace boost::phoenix::arg_names;
#include <boost/type_traits.hpp>
#include <boost/utility/result_of.hpp>
using boost::function_traits;
template <class T>
struct static_lambda {
static const T t;
// A static function that simply applies t
template <class arg1type, class arg2type>
static typename boost::result_of<T(arg1type,arg2type)>::type
apply(arg1type arg1,arg2type arg2){
return t(arg1,arg2);
}
// Conversion to a function pointer
template<class func_type>
operator func_type*() {
typedef typename function_traits<func_type>::arg1_type arg1type;
typedef typename function_traits<func_type>::arg2_type arg2type;
return &static_lambda<T>::apply<arg1type,arg2type>;
}
};
template <class T>
const T static_lambda<T>::t; // Default initialize the functional
template <class T>
static_lambda<T> make_static(T t) {return static_lambda<T>();}
#include <iostream>
#include <cstdio>
int main() {
int (*add) (int,int) = make_static(_1+_2);
std::cout<<add(10,15)<<"\n";
int c=5;
// int (*add_with_ref) (int,int) = make_static(_1+_2+ref(c)); causes compiler error as desired
}
1 ответ
- Нет способа сделать это чище. Вы вызываете функцию-член через нулевой указатель. Это все виды неопределенного поведения, но вы уже это знаете.
- Вы не можете знать, если функция Boost.Lambda не имеет состояния. Это черный ящик. Boost.Phoenix - это отдельная история. Он построен на Boost.Proto, инструментарии DSL, и Phoenix публикует свою грамматику и дает вам ловушки для анализа лямбда-выражений, которые он генерирует. Вы можете довольно легко написать алгоритм Proto для поиска терминалов с сохранением состояния и создания бомб во время компиляции, если он найдет их. (Но это не меняет мой ответ на № 1 выше.)
Вы сказали, что вам нравится полиморфный характер лямбда-функций Boost, но вы не используете это свойство в своем коде выше. Мое предложение: используйте C++11 лямбда-выражений. Те, которые не имеют состояния, уже имеют неявное преобразование в необработанные указатели функций. Это именно то, что вы ищете, ИМО.
=== UPDATE ===
Хотя вызов функции-члена через нулевой указатель - ужасная идея (не делайте этого, вы ослепнете), вы можете создать по умолчанию НОВЫЙ лямбда-объект того же типа, что и оригинал. Если вы объедините это с моим предложением в #2 выше, вы можете получить то, что вам нужно. Вот код:
#include <iostream>
#include <type_traits>
#include <boost/mpl/bool.hpp>
#include <boost/mpl/and.hpp>
#include <boost/phoenix.hpp>
namespace detail
{
using namespace boost::proto;
namespace mpl = boost::mpl;
struct is_stateless
: or_<
when<terminal<_>, std::is_empty<_value>()>,
otherwise<
fold<_, mpl::true_(), mpl::and_<_state, is_stateless>()>
>
>
{};
template<typename Lambda>
struct static_lambda
{
template<typename Sig>
struct impl;
template<typename Ret, typename Arg0, typename Arg1>
struct impl<Ret(Arg0, Arg1)>
{
static Ret apply(Arg0 arg0, Arg1 arg1)
{
return Lambda()(arg0, arg1);
}
};
template<typename Fun>
operator Fun*() const
{
return &impl<Fun>::apply;
}
};
template<typename Lambda>
inline static_lambda<Lambda> make_static(Lambda const &l)
{
static_assert(
boost::result_of<is_stateless(Lambda)>::type::value,
"Lambda is not stateless"
);
return static_lambda<Lambda>();
}
}
using detail::make_static;
int main()
{
using namespace boost::phoenix;
using namespace placeholders;
int c=5;
int (*add)(int,int) = make_static(_1+_2);
// We can even define arrays with the following syntax
static double (*const func_array[])(double,double) =
{
make_static(_1+_2),
make_static(_1*_2)
};
std::cout << func_array[0](10,15) << "\n";
std::cout << func_array[1](10,15);
// If you try to create a stateless lambda from a lambda
// with state, you trigger a static assertion:
int (*oops)(int,int) = make_static(_1+_2+42); // ERROR, not stateless
}
Отказ от ответственности: я не автор Феникса. Я не знаю, гарантируется ли конструктивность по умолчанию для всех лямбд без сохранения состояния.
Протестировано с MSVC-10.0.
Наслаждайтесь!