Вопрос по c++ – Статические функции из boost.lambda или boost.phoenix

8

Я регулярно использую boost.lambda (и phoenix) для определения лямбда-функций в C ++. Мне очень нравится их полиморфное свойство, простота их представления и то, как они делают функциональное программирование на C ++ намного проще. В некоторых случаях это даже чище и более читабельно (если вы привыкли их читать), чтобы использовать их для определения небольших функций и именования их в статической области видимости.

Способ хранения этих функционалов, который больше всего напоминает обычные функции, состоит в том, чтобы захватить их вboost::function

<code>const boost::function<double(double,double)> add = _1+_2;
</code>

Но проблема заключается в неэффективности выполнения этого. Хотяadd функция здесь не имеет состояния, возвращаемый лямбда-тип не пустой и егоsizeof больше 1 (такboost::function ctor по умолчанию и ctor копирования будут включатьnew). Я действительно сомневаюсь, что существует механизм со стороны компилятора или стороны надстройки, чтобы обнаруживать это отсутствие состояния и генерировать код, который эквивалентен использованию:

<code>double (* const add)(double,double) = _1+_2; //not valid right now
</code>

Можно, конечно, использовать C ++ 11auto, но тогда переменная не может быть передана без шаблонного контекста. Мне наконец удалось сделать почти то, что я хочу, используя следующий подход:

<code>#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
}
</code>

Скомпилировано с gcc 4.6.1. Вывод этой программы (независимо от уровня оптимизации):

<code>25
Segmentation fault
</code>

как и ожидалось. Здесь я сохраняю статический указатель на тип лямбда-выражения (максимально возможный для целей оптимизации) и инициализирую его какNULL, Таким образом, если вы попытаетесь «статизировать» лямбда-выражение с состоянием, вы обязательно получите ошибку во время выполнения. И если вы статизируете действительно лямбда-выражение без сохранения состояния, все сработает.

На вопрос (ы):

The method seems a bit dirty, can you think of any circumstance, or compiler assumption that will make this misbehave (expected behavior: work fine if lambda is stateless, segfault otherwise).

Can you think of any way that attempting this will cause a compiler error instead of a segfault when the lambda expression has state?

РЕДАКТИРОВАТЬ после ответа Эрика Ниблера:

<code>#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
}
</code>
IIRC, если вы используете Phoenix, вы можете сохранить результат вboost::phoenix::function а неboost::function и смягчить некоторую потерю эффективности (boost::phoenix::function являются типами POD и могут быть статически инициализированы во время компиляции). ildjarn
@ildjarn Спасибо за советы оboost::phoenix::functionэто должно быть полезно во многих случаях. Тем не менее, я все еще заинтересован в получении лямбда-функции для захвата лямбда-функции в собственном эквиваленте. Я не уверен, возможно ли вообще сделать это производство качественным, но я нахожу это интересным. enobayram

Ваш Ответ

1   ответ
11
There is no way to make this cleaner. You are calling a member function through a null pointer. This is all kinds of undefined behavior, but you know that already. You can't know if a Boost.Lambda function is stateless. It's a black box. Boost.Phoenix is a different story. It's built on Boost.Proto, a DSL toolkit, and Phoenix publishes its grammar and gives you hooks to introspect the lambda expressions it generates. You can quite easily write a Proto algorithm to look for stateful terminals and bomb out at compile time if it finds any. (But that doesn't change my answer to #1 above.)

что вам нравится полиморфный характер лямбда-функций Boost, но вы не используете это свойство в своем коде выше. Мое предложение: используйте лямбды C ++ 11. Те, у кого нет состояний, уже имеют неявное преобразование в необработанные указатели функций. Это именно то, что вы ищете, IMO.

===UPDATE===

Хотя вызов функции-члена через нулевой указатель является ужасной идеей (не делайте этого, вы ослепнете), выcan конструкция по умолчаниюNEW Лямбда-объект того же типа, что и оригинал. Если вы объедините это с моим предложением в № 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
}

Disclaimer: Я не автор Феникса. Я не знаю, гарантируется ли возможность построения по умолчанию для всех лямбд без сохранения состояния.

Протестировано с MSVC-10.0.

Наслаждайтесь!

Осторожнее там. Это не только не имеющие состояния лямбды Феникса, которые могут быть созданы по умолчанию; Ваше решение не будет ловить лямбда-выражения, которые захватывают местных жителей по значению, при условии, что типы этих местных жителей сами конструируются по умолчанию. Вам действительно нужно использоватьis_stateless Прото алгоритм я написал выше.
+1, нет ничего лучше авторитетного ответа. : -]
@EricNiebler Я только что протестировал компиляциюint (*add) (int,int) = make_static(_1+_2+localVariable); и он не компилируется (по желанию). Вы уверены в своем последнем комментарии? Это про буст версию (у меня это 1.48)? Или ты о чем-то еще говоришь? Кстати, я все еще думаю, что вашis_stateless метафункция очень полезна. enobayram
Я только что видел ваше обновление, я также пытался использовать лямбды Phoenix после прочтения вашего исходного ответа и заметил, что они поддерживают конструкцию по умолчанию для лямбд без сохранения состояния. Я также придумала решение, которое использует это свойство (проверьте мое редактирование). Хотя я думаю, что ваше решение лучше и более дидактично :) enobayram
@EricNiebler О полиморфном свойстве: я знаю, что я не использую это в приведенном выше коде, но вы можете представить себе случай, когда вам нужно передать полиморфную лямбду (в качестве посетителя) шаблонной функции, и эта функция создает вашу лямбду с различными типами и сохраняет их как обычные указатели функций. Вы не можете использовать лямбды с ++ 11 в этом крайнем случае :) enobayram

Похожие вопросы