8

私は定期的にboost.lambda(およびフェニックス)を使用してC++でラムダ関数を定義しています。私はそれらの多態性、表現の単純さ、そしてC++での関数型プログラミングを非常に簡単にする方法が本当に好きです。場合によっては、小さな関数を定義し、静的スコープで名前を付けるためにそれらを使用する方が、よりクリーンで読みやすくなります(それらを読み取ることに慣れている場合)。

従来の関数に最も類似しているこれらの汎関数を格納する方法は、それらをキャプチャすることです。boost::function

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

しかし、問題はそうすることの実行時の非効率性です。ここでのadd関数はステートレスですが、返されるラムダ型は空ではなくsizeof、1より大きいです(したがって、boost::functionデフォルトのctorと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

予想通り。ここでは、ラムダ式タイプへの静的ポインターを保持し(最適化の目的で可能な限りconst)、それをに初期化しNULLます。このように、状態を使用してラムダ式を「静的化」しようとすると、実行時エラーが発生します。そして、真にステートレスなラムダ式を静的化すると、すべてがうまくいきます。

質問について:

  1. メソッドは少し汚いようですが、これを誤動作させる状況やコンパイラの仮定を考えてみてください(予期される動作:ラムダがステートレスの場合は正常に動作し、それ以外の場合はセグメンテーション違反)。

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

1 に答える 1

11
  1. これをきれいにする方法はありません。nullポインタを介してメンバー関数を呼び出しています。これはあらゆる種類の未定義の動作ですが、すでにご存知でしょう。
  2. Boost.Lambda関数がステートレスであるかどうかはわかりません。それはブラックボックスです。Boost.Phoenixは別の話です。これはDSLツールキットであるBoost.Protoに基づいて構築されており、Phoenixはその文法を公開し、生成するラムダ式をイントロスペクトするためのフックを提供します。Protoアルゴリズムを非常に簡単に記述して、ステートフルターミナルを探し、コンパイル時に爆破することができます。(しかし、それは私の答えを上記の#1に変更しません。)

Boostのラムダ関数の多形性が好きだとおっしゃいましたが、上記のコードではそのプロパティを使用していません。私の提案:C++11ラムダを使用してください。ステートレスのものは、生の関数ポインタへの暗黙の変換をすでに持っています。それはまさにあなたが探しているものです、IMO。

===更新===

nullポインターを介してメンバー関数を呼び出すことはひどい考えですが(そうしないでください、盲目になります)、元の同じタイプの新しいラムダオブジェクトをデフォルトで構築できます。それを上記の#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でテスト済み。

楽しみ!

于 2012-09-07T22:26:15.127 に答える