7

Có cách nào để khôi phục thông tin kiểu từ một lambda với các tham số mặc định được lưu trữ trong một hàm std :: không có các tham số đó trong kiểu của nó không?Có thể sao chép std :: function có chứa lambda với các tham số mặc định không?

std::function<void()> f1 = [](int i = 0){}; 
std::function<void(int)> f2 = [](int i = 0){}; 
std::function<void(int)> f3 = f1; // error 
std::function<void()> f4 = f2;  // error 

Nhìn vào constructor sao chép std :: chức năng của, không có mẫu chuyên môn hóa từng phần với nhiều loại chức năng khác, vì vậy tôi muốn tưởng tượng thông tin này bị mất và nó chỉ là một trường hợp mà bạn không thể gán một hàm của một loại đối với một chức năng của một loại khác, ngay cả khi nội bộ chúng đều có thể gọi hàm. Điều này có đúng không? Có công việc nào để đạt được điều này không? Tôi đang nhìn vào std :: function :: target, nhưng không có may mắn, tôi không có chuyên gia về các loại chức năng và con trỏ.

Một lưu ý phụ, f1 (hoặc lambda) liên kết thông số mặc định như thế nào?

+0

Bạn dự định gọi f1 hoặc f2 như thế nào (Với giá trị mặc định)? – nakiya

+1

@nakiya Nó dành cho việc triển khai tín hiệu và khe yêu cầu bản sao của khe (chức năng) khi thực hiện kết nối, tín hiệu sẽ gọi khe với thông số được truyền từ trang gọi của tín hiệu. Nó sẽ được tốt đẹp để cho phép khe với các thông số mặc định để kết nối với bất kỳ tín hiệu với một chữ ký có thể gọi nó. –

Trả lời

4

Không, điều đó là không thể, vì đối số mặc định là thuộc tính của một tập hợp các khai báo của hàm, không phải của chính hàm đó. Nói cách khác, đây là hoàn toàn hợp pháp C++:

A.cpp

int f(int i = 42); 

const int j = f(); // will call f(42) 

B.cpp

int f(int i = 314); 

const int k = f(); // will call f(314) 

F.cpp

int f(int i = 0) 
{ 
    return i; 
} 

const int x = f(); // will call f(0) 

Tất cả những thứ này có thể được liên kết với nhau rất tốt.

Điều đó có nghĩa là không thể bằng cách nào đó "truy xuất" đối số mặc định từ một hàm.

Bạn có thể làm tương đương với f4 = f2 sử dụng std::bind và cung cấp các tham số mặc định của riêng mình, như thế này:

std::function<void()> f4 = std::bind(f2, 42); 

[Live example]

Tuy nhiên, không có cách nào để có được một cái gì đó tương đương với f3 = f1.

+0

"Bạn có thể làm tương đương với' f4 = f2' bằng cách sử dụng 'std :: bind'". Bạn có thể cung cấp một ví dụ về điều này? – nakiya

+1

@nakiya một cái gì đó như: 'std :: function f4 = std :: bind (f2, 42)' –

1
template<class...Sigs> 
strucct functions:std::function<Sigs>...{ 
    using std::function<Sigs>::operator()...; 
    template<class T, 
    std::enable_if<!std::is_same<std::decay_t<T>,fundtions>{}>,int> =0 
    > 
    functions(T&&t): 
    std::function<Sigs>(t)... 
    {} 
}; 

ở trên là bản phác thảo C++ 17 của đối tượng thô cam lưu trữ nhiều hơn operator().

Một cách hiệu quả hơn sẽ chỉ lưu trữ đối tượng một lần, nhưng lưu trữ cách gọi nó theo nhiều cách. Và tôi đã bỏ qua rất nhiều chi tiết.

Nó không thực sự là std::function, nhưng là loại tương thích; chức năng std chỉ lưu trữ một cách để gọi đối tượng.

Dưới đây là "chế độ xem chức năng" có bất kỳ số chữ ký nào. Nó không sở hữu đối tượng được gọi.

template<class Sig> 
struct pinvoke_t; 
template<class R, class...Args> 
struct pinvoke_t<R(Args...)> { 
    R(*pf)(void*, Args&&...) = 0; 
    R invoke(void* p, Args...args)const{ 
     return pf(p, std::forward<Args>(args)...); 
    } 
    template<class F, std::enable_if_t<!std::is_same<pinvoke_t, std::decay_t<F>>{}, int> =0> 
    pinvoke_t(F& f): 
     pf(+[](void* pf, Args&&...args)->R{ 
      return (*static_cast<F*>(pf))(std::forward<Args>(args)...); 
     }) 
    {} 
    pinvoke_t(pinvoke_t const&)=default; 
    pinvoke_t& operator=(pinvoke_t const&)=default; 
    pinvoke_t()=default; 
}; 

template<class...Sigs> 
struct invoke_view:pinvoke_t<Sigs>... 
{ 
    void* pv = 0; 
    explicit operator bool()const{ return pv; } 
    using pinvoke_t<Sigs>::invoke...; 
    template<class F, std::enable_if_t<!std::is_same<invoke_view, std::decay_t<F>>{}, int> =0> 
    invoke_view(F&& f): 
     pinvoke_t<Sigs>(f)... 
    {} 
    invoke_view()=default; 
    invoke_view(invoke_view const&)=default; 
    invoke_view& operator=(invoke_view const&)=default; 
    template<class...Args> 
    decltype(auto) operator()(Args&&...args)const{ 
     return invoke(pv, std::forward<Args>(args)...); 
    } 
}; 

Live example.

Tôi sử dụng C++ 17 using ... vì việc triển khai cây nhị phân trong C++ 14 rất xấu.

Đối với trường hợp sử dụng của bạn, nó sẽ looke như:

auto func_object = [](int i = 0){}; 
invoke_view<void(), void(int)> f1 = func_object; 
std::function<void(int)> f3 = f1; // works 
std::function<void()> f4 = f1;  // works 

lưu ý rằng việc thiếu quản lý đời trong invoke_view có nghĩa là ở trên chỉ hoạt động khi func_object tiếp tục tồn tại. (Nếu chúng ta tạo một khung nhìn gọi đến một khung nhìn gọi, khung nhìn gọi "bên trong" cũng được lưu trữ bởi con trỏ, vì vậy phải tiếp tục tồn tại; không phải trường hợp nếu chúng ta lưu trữ khung nhìn gọi trong một hàm std).

Quản lý lâu dài mục tiêu, được thực hiện đúng, mất một chút công việc. Bạn sẽ muốn sử dụng một tối ưu hóa bộ đệm nhỏ với một con trỏ thông minh tùy chọn hoặc một cái gì đó để có được hiệu suất hợp lý với lambdas nhỏ và tránh các chi phí của việc phân bổ đống.

Một giải pháp phân bổ heap đơn giản luôn luôn heap sẽ thay thế void* bằng unique_ptr<void, void(*)(void*)> và lưu trữ { new T(t), [](void* ptr){static_cast<T*>(ptr)->~T();} } trong đó (hoặc tương tự).

Giải pháp đó làm cho đối tượng hàm chỉ di chuyển; làm cho nó có thể sao chép cũng yêu cầu loại bỏ một hoạt động sao chép.

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