2012-04-13 16 views
8

Tôi thường xuyên sử dụng boost.lambda (và phoenix) để định nghĩa hàm lambda trong C++. Tôi thực sự thích tài sản đa hình của họ, sự đơn giản của sự trình bày của họ và cách họ tạo lập trình hàm trong C++ dễ dàng hơn rất nhiều. Trong một số trường hợp, nó thậm chí còn sạch hơn và dễ đọc hơn (nếu bạn đang sử dụng để đọc chúng) để sử dụng chúng để xác định các chức năng nhỏ và đặt tên chúng trong phạm vi tĩnh.Các hàm tĩnh từ boost.lambda hoặc boost.phoenix

Cách để lưu trữ những functionals tương tự như chức năng thông thường nhất là để nắm bắt chúng trong một boost::function

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

Nhưng vấn đề là không hiệu quả thời gian chạy của làm như vậy. Mặc dù hàm add ở đây là không quốc tịch, kiểu lambda trả lại không trống và sizeof lớn hơn 1 (vì vậy boost::function ctor mặc định và ctor sao chép sẽ liên quan đến new). Tôi thực sự nghi ngờ rằng có một cơ chế từ trình biên dịch hoặc một bên của động lực để phát hiện statelessness này và tạo ra mã tương đương với sử dụng:

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

Người ta có thể sử dụng tất nhiên C++ 11 auto, nhưng sau đó biến không thể được thông qua xung quanh các bối cảnh không có khuôn mẫu. Cuối cùng tôi đã quản lý để làm hầu hết những gì tôi muốn, bằng cách sử dụng phương pháp sau đây:

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

Biên soạn với gcc 4.6.1 Kết quả của chương trình này là (không phụ thuộc vào mức độ tối ưu hóa):

25 
Segmentation fault 

như mong đợi. Ở đây, tôi giữ một con trỏ tĩnh đến kiểu biểu thức lambda (như const càng tốt cho mục đích tối ưu hóa) và khởi tạo nó thành NULL. Bằng cách này, nếu bạn cố gắng "tĩnh" một biểu thức lambda với trạng thái, bạn chắc chắn sẽ nhận được một lỗi thời gian chạy. Và nếu bạn tĩnh một biểu thức lambda thực sự không trạng thái, mọi thứ sẽ hoạt động.

On cho câu hỏi (s):

  1. Phương pháp này có vẻ hơi bẩn, bạn có thể nghĩ ra bất kỳ hoàn cảnh nào, hoặc biên dịch giả định rằng sẽ làm cho misbehave này (hành vi mong đợi: hoạt động tốt nếu lambda là không quốc tịch, segfault khác).

  2. Bạn có thể nghĩ ra bất kỳ cách nào để thực hiện điều này sẽ gây ra lỗi trình biên dịch thay vì segfault khi biểu thức lambda có trạng thái không?

EDIT sau khi câu trả lời Eric Niebler của:

#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

IIRC, nếu bạn đang sử dụng Phoenix, bạn có thể lưu trữ kết quả bên trong hàm 'boost :: phoenix ::' chứ không phải hàm' boost ::' và giảm thiểu một số tổn thất hiệu quả ('boost :: phoenix: : function' là các loại POD và có thể được khởi tạo tĩnh tại thời gian biên dịch). – ildjarn

+0

@ildjarn Cảm ơn những người đứng đầu về 'boost :: phoenix :: function', đó là ràng buộc hữu ích trong nhiều trường hợp. Tôi vẫn quan tâm đến việc có được hàm lambda tương đương với native (hiệu năng thời gian chạy hiệu năng). Tôi không chắc chắn nếu nó có thể làm cho chất lượng sản xuất này, nhưng tôi tìm thấy sự theo đuổi thú vị. – enobayram

Trả lời

11
  1. Không có cách nào để làm sạch này. Bạn đang gọi một hàm thành viên thông qua một con trỏ null. Đây là tất cả các loại hành vi không xác định, nhưng bạn đã biết điều đó rồi.
  2. Bạn không thể biết chức năng Boost.Lambda có trạng thái không quốc tịch hay không. Đó là một hộp đen. Boost.Phoenix là một câu chuyện khác. Nó được xây dựng trên Boost.Proto, một bộ công cụ DSL và Phoenix xuất bản ngữ pháp của nó và cung cấp cho bạn các móc để nhìn vào các biểu thức lambda mà nó tạo ra. Bạn có thể khá dễ dàng viết một thuật toán Proto để tìm các thiết bị đầu cuối stateful và bom ra tại thời gian biên dịch nếu nó tìm thấy bất kỳ. (Nhưng điều đó không thay đổi câu trả lời của tôi cho # 1 ở trên.)

Bạn đã nói bạn thích bản chất đa hình của hàm lambda Boost, nhưng bạn không sử dụng thuộc tính đó trong mã của bạn ở trên. Đề xuất của tôi: sử dụng C++ 11 lambdas. Những người không quốc tịch đã có một chuyển đổi tiềm ẩn cho con trỏ hàm nguyên. Nó chỉ là những gì bạn đang tìm kiếm, IMO.

=== CẬP NHẬT ===

Mặc dù gọi một hàm thành viên thông qua một con trỏ null là một ý tưởng khủng khiếp (không làm điều đó, bạn sẽ bị mù), bạn có thể default- xây dựng một đối tượng lambda NEW lambda cùng loại của bản gốc. Nếu bạn kết hợp điều đó với đề xuất của tôi ở phần 2 ở trên, bạn có thể nhận được những gì bạn đang theo dõi. Dưới đây là các mã:

#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: Tôi không phải là tác giả của Phoenix. Tôi không biết liệu khả năng xây dựng mặc định có được đảm bảo cho tất cả các lambdas không quốc tịch hay không.

Được thử nghiệm với MSVC-10.0.

Tận hưởng!

+2

+1, không có gì giống như câu trả lời có thẩm quyền. : -] – ildjarn

+0

Tôi vừa xem bản cập nhật của bạn, tôi cũng đã cố gắng sử dụng Phoenix lambdas sau khi đọc câu trả lời gốc của bạn và nhận thấy rằng họ hỗ trợ xây dựng mặc định trên lambdas không quốc tịch. Tôi cũng đã đưa ra một giải pháp sử dụng thuộc tính đó (kiểm tra chỉnh sửa của tôi). Mặc dù tôi nghĩ rằng giải pháp của bạn là tốt hơn và giáo khoa hơn :) – enobayram

+2

Cẩn thận ở đó. Nó không chỉ là các pho tượng Phoenix không quốc tịch được xây dựng mặc định; giải pháp của bạn sẽ không bắt được lambdas thu hút người dân địa phương theo giá trị, miễn là các loại của những người dân địa phương này là bản thân họ có thể xây dựng được mặc định. Bạn thực sự cần sử dụng thuật toán Proto 'is_stateless' tôi đã viết ở trên. –

Các vấn đề liên quan