2012-02-11 56 views
17

Một hàm có tên test mất tiêu chuẩn :: function <> làm tham số của nó.C++ 11 variadic std :: tham số chức năng

template<typename R, typename ...A> 
void test(std::function<R(A...)> f) 
{ 
    // ... 
} 

Nhưng, nếu tôi làm như sau:

void foo(int n) { /* ... */ } 

// ... 

test(foo); 

Compiler (gcc 4.6.1) nói no matching function for call to test(void (&)(int)).

Để làm dòng cuối cùng test(foo) biên dịch và hoạt động bình thường, làm cách nào tôi có thể sửa đổi chức năng test()? Trong chức năng test(), tôi cần f với loại std :: function <>.

Ý tôi là, có bất kỳ thủ thuật mẫu nào để cho trình biên dịch xác định chữ ký của hàm (ví dụ: foo) và tự động chuyển đổi thành std::function<void(int)> không?

CHỈNH SỬA

Tôi muốn thực hiện công việc này cho lambdas (cả được nêu và không quốc tịch).

Trả lời

11

Dường như bạn muốn sử dụng quá tải

template<typename R, typename ...A> 
void test(R f(A...)) 
{ 
    test(std::function<R(A...)>(f)); 
} 

thực hiện đơn giản này sẽ chấp nhận hầu hết nếu không phải tất cả các chức năng bạn sẽ cố gắng để vượt qua. Các chức năng lạ sẽ bị từ chối (như void(int...)). Nhiều công việc hơn sẽ mang đến cho bạn thêm sự hào phóng.

+0

gì về lambdas (cả nói và không quốc tịch)? –

+4

@Daniel bạn đã hết may mắn. Hoặc làm cho 'test' một mẫu chấp nhận bất cứ điều gì (' T'). 'std :: function' sẽ không loại bỏ các đối tượng hàm không tương thích hoàn toàn, vì vậy mục tiêu giới hạn kiểu tham số của hàm mẫu có vẻ không hữu ích đối với tôi ở đây. –

+1

Tôi đã thử với '(T)', nhưng, làm thế nào nó có thể được thực hiện cho 'std :: function '? Có vẻ như tôi có thể nhận được 'R' bằng cách sử dụng' std :: result_of <> ', nhưng' A ... '? –

4

Thường không được chấp nhận khi chấp nhận std::function theo giá trị trừ khi bạn đang ở 'phân tách nhị phân' (ví dụ: thư viện động, API 'đục') vì bạn vừa chứng kiến ​​chúng chơi tàn phá quá tải. Khi một hàm thực tế lấy một giá trị std::function thì nó thường là gánh nặng của người gọi để xây dựng đối tượng để tránh các vấn đề quá tải (nếu hàm bị quá tải ở tất cả).

Tuy nhiên, vì bạn đã viết mẫu, có khả năng trường hợp bạn không sử dụng std::function (làm loại tham số) vì lợi ích của loại tẩy xóa. Nếu những gì bạn muốn làm là kiểm tra functors tùy ý sau đó bạn cần một số đặc điểm cho điều đó. Ví dụ. Boost.FunctionTypes có các đặc điểm như result_typeparameter_types. Một tối thiểu, chức năng ví dụ:

#include <functional> 

#include <boost/function_types/result_type.hpp> 
#include <boost/function_types/parameter_types.hpp> 
#include <boost/function_types/function_type.hpp> 

template<typename Functor> 
void test(Functor functor) // accept arbitrary functor! 
{ 
    namespace ft = boost::function_types; 

    typedef typename ft::result_type<Functor>::type result_type; 
    typedef ft::parameter_types<Functor> parameter_types; 
    typedef typename boost::mpl::push_front< 
     parameter_types 
     , result_type 
    >::type sequence_type; 
    // sequence_type is now a Boost.MPL sequence in the style of 
    // mpl::vector<int, double, long> if the signature of the 
    // analyzed functor were int(double, long) 

    // We now build a function type out of the MPL sequence 
    typedef typename ft::function_type<sequence_type>::type function_type; 

    std::function<function_type> function = std::move(functor); 
} 

Là một lưu ý cuối cùng, tôi không khuyên bạn nên introspecting functors (ví dụ: thúc giục cho loại kết quả của họ và loại đối số) trong trường hợp tổng quát như mà chỉ đơn giản không làm việc cho functors đa hình. Xem xét một số quá tải operator(): sau đó không có loại kết quả hoặc loại đối số 'chuẩn'. Với C++ 11, tốt hơn là 'háo hức' chấp nhận bất kỳ loại functor nào, hoặc hạn chế chúng bằng cách sử dụng các kỹ thuật như SFINAE hoặc static_assert tùy theo nhu cầu và sau đó (khi có thông số) để sử dụng std::result_of để kiểm tra loại kết quả cho một bộ đối số đã cho. Một trường hợp hạn chế lên phía trước là mong muốn là khi mục đích là để lưu trữ các functors vào ví dụ. một vùng chứa std::function<Sig>.

Để hiểu được ý của tôi trong đoạn trước, nó đủ để kiểm tra đoạn mã trên với các hàm funcorphic.

5

std::function triển khai Giao diện có thể gọi, tức là giao diện trông giống như một hàm, nhưng điều đó không có nghĩa là bạn phải yêu cầu đối tượng có thể gọi là std::function s.

template< typename F > // accept any type 
void test(F const &f) { 
    typedef std::result_of< F(args) >::type R; // inspect with traits queries 
} 

Nhập vịt là chính sách tốt nhất trong lập trình meta mẫu. Khi chấp nhận một đối số mẫu, không xác định và chỉ để cho khách hàng thực hiện giao diện.

Nếu bạn thực sự cần một std::function ví dụ để lại nhắm mục tiêu biến hoặc một cái gì đó điên như thế, và bạn biết đầu vào là một con trỏ hàm thô, bạn có thể phân hủy một loại con trỏ hàm nguyên và reconsitute nó thành một std::function.

template< typename R, typename ... A > 
void test(R (*f)(A ...)) { 
    std::function< R(A ...) > internal(f); 
} 

Bây giờ người dùng không thể chuyển số std::function vì đã được đóng gói trong hàm. Bạn có thể giữ mã hiện tại của bạn như một tình trạng quá tải khác và chỉ ủy quyền cho điều đó, nhưng hãy cẩn thận để giữ cho các giao diện đơn giản.

Đối với lambdas trạng thái, tôi không biết cách xử lý trường hợp đó. Chúng không phân hủy chức năng con trỏ và theo như tôi biết, các kiểu đối số không thể được truy vấn hoặc suy luận. Thông tin này là cần thiết để khởi tạo std::function, để tốt hơn hoặc tệ hơn.

+1

Tôi tin rằng cụm từ chính xác sẽ là ràng buộc động - sử dụng cụm từ 'duck typing' đã bị từ chối – serup

+0

@serup Google => "Ràng buộc muộn, hoặc ràng buộc động là cơ chế lập trình máy tính trong đó phương thức được gọi khi đối tượng hoặc hàm được gọi với các đối số được tra cứu theo tên tại thời gian chạy. " Không áp dụng cho mẫu. – Potatoswatter

+0

Sau đó, tại sao sử dụng thuật ngữ gõ vịt? – serup

2

Đây là hình ảnh cũ và tôi dường như không thể tìm thấy nhiều về cùng một chủ đề, vì vậy tôi nghĩ tôi sẽ tiếp tục và ghi chú.

Biên soạn trên GCC 4.8.2, các công việc sau:

template<typename R, typename... A> 
R test(const std::function<R(A...)>& func) 
{ 
    // ... 
} 

Tuy nhiên, bạn không thể chỉ gọi nó bằng cách đi qua trong con trỏ của bạn, lambdas, vv Tuy nhiên, 2 ví dụ cả hai công việc sau đây với nó:

test(std::function<void(int, float, std::string)>(
     [](int i, float f, std::string s) 
     { 
      std::cout << i << " " << f << " " << s << std::endl; 
     })); 

Ngoài ra:

void test2(int i, float f, std::string s) 
{ 
    std::cout << i << " " << f << " " << s << std::endl; 
} 

// In a function somewhere: 
test(std::function<void(int, float, std::string)>(&test2)); 

Nhược điểm của những nên đứng ra khá rõ ràng: yo u phải khai báo rõ ràng hàm std :: cho chúng, có thể trông hơi xấu xí.

Điều đó nói rằng, mặc dù, tôi đã ném nó cùng với một tuple được mở rộng để gọi hàm đến, và nó hoạt động, chỉ đòi hỏi một chút rõ ràng hơn những gì bạn đang làm gọi hàm test.

Ví dụ mã trong đó có điều tuple, nếu bạn muốn chơi với nó: http://ideone.com/33mqZA

+1

Chỉ cần cho đá, tôi đã đưa ra một phương pháp sử dụng index_sequence (được thêm vào trong C++ 14, nhưng dễ thực hiện trong C++ 11) và một cấu trúc kiểu hàm function_traits để tìm ra một thứ gì đó sẽ sử dụng bất kỳ lambda, functor hoặc hàm nào và sử dụng nó. [http://ideone.com/LNpj74](http://ideone.com/LNpj74) hiển thị ví dụ hoạt động về điều đó. Lưu ý, tuy nhiên, đối với functors với 2+ operator() bị quá tải, nó yêu cầu một giao diện bổ sung chỉ định các kiểu để sử dụng. Đã không thử nó với functors đa hình, nhưng tôi hy vọng rằng sẽ gây ra các vấn đề ... – user3694249

+0

Như Potatoswatter ám chỉ ở trên, khi các mẫu có sẵn, sử dụng một 'std :: function' là một đống chi phí phân bổ động và boilerplate không có lợi ích. Tốt hơn là chỉ cần tạo một mẫu hàm chung có thể chấp nhận một lambda, hơn là kéo vào 'std :: function' trên khắp nơi. –

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