【问题标题】:Static functions from boost.lambda or boost.phoenixboost.lambda 或 boost.phoenix 的静态函数
【发布时间】:2012-04-13 16:46:22
【问题描述】:

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

存储这些与传统函数最相似的函数的方法是将它们捕获到boost::function

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

但问题是这样做的运行时效率低下。尽管这里的add 函数是无状态的,但是返回的lambda 类型不为空并且它的sizeof 大于1(所以boost::function 默认ctor 和复制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。这样,如果你尝试用状态“静态化”一个 lambda 表达式,你肯定会得到一个运行时错误。如果你静态化一个真正无状态的 lambda 表达式,一切都会顺利。

关于问题:

  1. 该方法似乎有点脏,您能想到任何情况或编译器假设会导致此行为不端(预期行为:如果 lambda 是无状态的,则可以正常工作,否则会出现段错误)。

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

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

【问题讨论】:

  • IIRC,如果您使用的是 Phoenix,您可以将结果存储在 boost::phoenix::function 而不是 boost::function 中并减轻一些效率损失(boost::phoenix::function 是 POD 类型,可以静态在编译时初始化)。
  • @ildjarn 感谢您对boost::phoenix::function 的提醒,这在许多情况下一定很有用。不过,我仍然对获取本机等效(运行时性能方面)的 lambda 函数捕获感兴趣。我不确定是否有可能达到这种生产质量,但我觉得追求很有趣。

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


【解决方案1】:
  1. 没有办法让这个更干净。您正在通过空指针调用成员函数。这是各种未定义的行为,但您已经知道了。
  2. 您无法知道 Boost.Lambda 函数是否是无状态的。这是一个黑匣子。 Boost.Phoenix 是另一回事。它建立在 DSL 工具包 Boost.Proto 之上,Phoenix 发布了它的语法并为您提供了内省它生成的 lambda 表达式的钩子。你可以很容易地编写一个 Proto 算法来查找有状态的终端,如果找到的话,可以在编译时将其轰出。 (但这并不会改变我对上面 #1 的回答。)

您说您喜欢 Boost 的 lambda 函数的多态性,但您没有在上面的代码中使用该属性。我的建议:使用 C++11 lambda。无状态的已经隐式转换为原始函数指针。这正是您正在寻找的,IMO。

===更新===

虽然通过空指针调用成员函数是一个糟糕的想法(不要这样做,你会失明),你可以默认构造一个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
}

免责声明:我不是 Phoenix 的作者。我不知道是否所有无状态 lambda 都能保证默认可构造性。

使用 MSVC-10.0 测试。

享受吧!

【讨论】:

  • 我刚刚看到您的更新,在阅读您的原始答案后,我还尝试使用 Phoenix lambdas,并注意到它们支持无状态 lambdas 上的默认构造。我还提出了一个使用该属性的解决方案(检查我的编辑)。虽然我认为你的解决方案更好,更具说教性:)
  • 小心点。不仅是无状态的 Phoenix lambdas 是默认可构造的;您的解决方案不会捕获按值捕获本地变量的 lambda,只要这些本地变量的类型本身是默认可构造的。你真的需要使用我上面写的is_statelessProto算法。
  • @EricNiebler 我刚刚测试了编译int (*add) (int,int) = make_static(_1+_2+localVariable);,但它不能编译(如所愿)。你确定你最后的评论吗?是关于增强版本(我的是 1.48)吗?还是您在谈论其他事情?顺便说一句,我仍然认为您的is_stateless 元函数非常有用。
  • @EricNiebler 关于多态属性:我知道我没有在上面的代码中使用它,但是您可以想象一个需要将多态 lambda(作为访问者)传递给模板的情况函数,并且该函数使用不同类型实例化您的 lambda,并将它们存储为常规函数指针。在这种极端情况下,您不能使用 c++11 lambda :)
猜你喜欢
  • 1970-01-01
  • 2013-05-03
  • 1970-01-01
  • 2022-01-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多