2016-06-09 17 views
10

Xét đoạn mã sau:Sao chép một hàm lambda với các thông số mặc định cho một biến

#include <iostream> 
#include <functional> 
using namespace std; 

int main() { 
    auto f = [](int a = 3) {cout << a << endl; }; 
    f(2); // OK 
    f(); // OK 

    auto g = f; 
    g(2); // OK 
    g(); // OK 

    function<void(int)> h = f; 
    h(2); // OK 
    h(); // Error! How to make this work? 

    return 0; 
} 

Làm thế nào tôi có thể tuyên bố h cư xử giống như fg?

+0

'auto h = f;'? – MikeCAT

+0

Tôi không nghĩ rằng 'std :: function' có bất kỳ hỗ trợ tham số mặc định nào. – chris

+0

@MikeCAT Tôi biết rằng :) Cách chính xác là gì nếu tôi muốn chỉ định đầy đủ loại, nếu có? –

Trả lời

12

std::function có một chữ ký cố định. Đây là một lựa chọn thiết kế, không phải là một yêu cầu khó khăn. Viết một pseudo std::function hỗ trợ nhiều chữ ký không phải là khó khăn:

template<class...Sigs> 
struct functions; 

template<> 
struct functions<> { 
    functions()=default; 
    functions(functions const&)=default; 
    functions(functions&&)=default; 
    functions& operator=(functions const&)=default; 
    functions& operator=(functions&&)=default; 
private: 
    struct never_t {private:never_t(){};}; 
public: 
    void operator()(never_t)const =delete; 

    template<class F, 
    std::enable_if_t<!std::is_same<std::decay_t<F>, functions>{}, int>* =nullptr 
    > 
    functions(F&&) {} 
}; 

template<class S0, class...Sigs> 
struct functions<S0, Sigs...>: 
    std::function<S0>, 
    functions<Sigs...> 
{ 
    functions()=default; 
    functions(functions const&)=default; 
    functions(functions&&)=default; 
    functions& operator=(functions const&)=default; 
    functions& operator=(functions&&)=default; 
    using std::function<S0>::operator(); 
    using functions<Sigs...>::operator(); 
    template<class F, 
    std::enable_if_t<!std::is_same<std::decay_t<F>, functions>{}, int>* =nullptr 
    > 
    functions(F&& f): 
    std::function<S0>(f), 
    functions<Sigs...>(std::forward<F>(f)) 
    {} 
}; 

sử dụng:

auto f = [](int a = 3) {std::cout << a << std::endl; }; 

functions<void(int), void()> fs = f; 
fs(); 
fs(3); 

Live example.

Điều này sẽ tạo một bản sao riêng biệt của lambda của bạn cho mỗi quá tải. Nó thậm chí có thể có lambdas khác nhau cho quá tải khác nhau với đúc cẩn thận.

Bạn có thể viết một cái không làm điều này, nhưng về cơ bản nó sẽ yêu cầu thực hiện lại std::function với trạng thái nội bộ sáng hơn.

Phiên bản nâng cao hơn ở trên sẽ tránh sử dụng thừa kế tuyến tính, vì kết quả là mã O (n^2) và độ sâu mẫu đệ quy O (n) trên số chữ ký. Một thừa kế cây nhị phân cân bằng giảm xuống đến mã O (n lg n) được tạo ra và độ sâu O (lg n).

Phiên bản cường độ công nghiệp sẽ lưu trữ thông tin trong lambda một lần, sử dụng tối ưu hóa đối tượng nhỏ, có hướng dẫn giả vtable sử dụng chiến lược thừa kế nhị phân để gửi cuộc gọi hàm và lưu trữ con trỏ hàm được gửi đến trong giả vtable. Nó sẽ lấy không gian O (# chữ ký) * sizeof (con trỏ hàm) trong cơ sở mỗi lớp (không phải mỗi trường hợp), và sử dụng khoảng phí trên mỗi trường hợp như std::function.

Nhưng điều đó hơi nhiều đối với bài đăng SO, phải không?

start of it

+3

bạn vừa viết câu trả lời này dưới 10mins hay bạn đã sao chép nó từ đoạn trích C++? –

+1

@BryanChen đã viết nó. Biên soạn lần đầu tiên, đó là phần đáng ngạc nhiên. Nếu tôi sử dụng một đoạn văn được viết sẵn, tôi hy vọng tôi sẽ tránh được sự thừa kế tuyến tính không hiệu quả đó!Tôi đã làm những việc như thế này trước đây, với poly-lambdas mà không sử dụng loại tẩy xoá, nhưng tôi không nghĩ rằng tôi đã thực hiện một poly-'std :: function'. – Yakk

+0

@Yakk Giải pháp rất thú vị! –

2

Tôi thích giải pháp tốt đẹp bởi @Yakk đề xuất.

Điều đó nói rằng, bạn có thể làm điều gì đó tương tự bằng cách tránh các std::function s và sử dụng một chức năng proxy cho các lambda, các mẫu variadic và std::forward, vì nó sau:

#include<functional> 
#include<utility> 
#include<iostream> 

template<typename... Args> 
void f(Args&&... args) { 
    auto g = [](int a = 3) { std::cout << a << std::endl; }; 
    g(std::forward<Args>(args)...); 
}; 

int main() { 
    f(2); 
    f(); 
} 

Đặt những ý tưởng trên trong một functor và bạn sẽ có một đối tượng giống như chức năng gần như giống nhau.

Có vấn đề là các thông số không được xác minh nghiêm ngặt.
Dù sao, nó sẽ thất bại trong thời gian biên dịch nếu bị lạm dụng.

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