来自boost.lambda或boost.phoenix的静态函数

时间:2012-04-13 16:46:23

标签: c++ boost lambda boost-phoenix boost-proto

我经常使用boost.lambda(和phoenix)在C ++中定义lambda函数。我非常喜欢它们的多态属性,它们表示的简单性以及它们在C ++中进行函数式编程的方式变得如此简单。在某些情况下,它甚至更清晰,更易读(如果您习惯于阅读它们),可以使用它们来定义小函数并在静态范围内命名它们。

存储这些类似于传统功能的功能的方法是在boost::function

中捕获它们
const boost::function<double(double,double)> add = _1+_2;

但问题是这样做的运行时效率低下。尽管此处add函数是无状态的,但返回的lambda类型不为空且其sizeof大于1(因此boost::function默认ctor和copy ctor将涉及new )。我真的怀疑编译器或boost方面有一种机制来检测这种无状态并生成相当于使用的代码:

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

正如所料。在这里,我保留一个指向lambda表达式类型的静态指针(尽可能为const用于优化目的)并将其初始化为NULL。这样,如果您尝试使用state“静态化”lambda表达式,您肯定会遇到运行时错误。如果你静态化了一个真正无状态的lambda表达式,一切都会成功。

关于问题:

  1. 这个方法看起来有点脏,你能想到会导致这种行为异常的任何情况或编译器假设(预期的行为:如果lambda是无状态的,则工作正常,否则会出现段错误。)

  2. 当lambda表达式有状态时,你能想到尝试这种方法会导致编译器错误而不是段错误吗?

  3. 在Eric Niebler的回答之后编辑:

    #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 个答案:

答案 0 :(得分:11)

  1. 没有办法让这个更干净。您正通过空指针调用成员函数。这是各种未定义的行为,但你已经知道了。
  2. 您无法知道Boost.Lambda函数是否为无状态。这是一个黑盒子。 Boost.Phoenix是一个不同的故事。它基于Boost.Proto构建,这是一个DSL工具包,Phoenix发布了它的语法,并为您提供了内省它生成的lambda表达式的钩子。您可以非常轻松地编写Proto算法来查找有状态终端,并在编译时发现它,如果找到任何终端。 (但这并没有改变我对上面#1的回答。)
  3. 你说你喜欢Boost的lambda函数的多态性,但是你在上面的代码中没有使用该属性。我的建议:使用C ++ 11 lambda。无状态的已经有一个隐式转换为原始函数指针。这正是您正在寻找的,IMO。

    <强> === UPDATE ===

    尽管通过空指针调用成员函数是一个可怕的想法(不要这样做,你会失明),你可以默认构建一个 NEW 原始相同类型的lambda对象。如果你将它与上面#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
    }
    

    免责声明:我不是凤凰城的作者。我不知道所有无状态lambda的默认构造能力是否得到保证。

    使用MSVC-10.0进行测试。

    享受!