Статические функции из boost.lambda или boost.phoenix

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

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

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

Но проблема заключается в неэффективности этого во время выполнения. Несмотря на то, что функция addздесь не имеет состояния, возвращаемый лямбда-тип не является пустым и его sizeofбольше 1 (поэтому boost::functionctor по умолчанию и copy 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. Таким образом, если вы попытаетесь «статизировать» лямбда-выражение с помощью состояния, вы обязательно получите ошибку времени выполнения. И если вы статифицируете действительно лямбда-выражение без сохранения состояния, все получится.

К вопросу (вопросам):

  1. Метод кажется немного грязным, можете ли вы придумать какое-либо обстоятельство или предположение компилятора, которое приведет к неправильному поведению (ожидаемое поведение: работает нормально, если лямбда не имеет состояния, иначе segfault) .

  2. Можете ли вы придумать способ, при котором попытка сделать это вызовет ошибку компилятора, а не ошибку сегментации, когда лямбда-выражение имеет состояние?

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

#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
}
8
задан Eric Niebler 25 September 2012 в 06:43
поделиться