2017-07-21 17 views
8

Có một số câu hỏi về SO liên quan đến việc truyền lambdas đến std::function s, nhưng tôi chưa thấy một gói sử dụng gói tham số cho danh sách đối số. Điều này có vẻ bị hỏng trên phiên bản g ++ của tôi (7.1.1-4) và có thể nó không được hỗ trợ. Vì vậy, điều này là hợp pháp c + + 17 (theo tiêu chuẩn)? Nếu không, tại sao?Truyền lambda đến std :: function with parameter package

#include <functional> 

template <typename TReturn, typename ... TArgs> 
void Functor(std::function<TReturn (TArgs...)> f) {} 

int main(int argc, char * argv[]) { 
    auto x = [] (int a, int b) { return a * b; }; 
    Functor<int, int, int>(x); 
    return 0; 
} 

Mã trên không biên dịch vì không khấu trừ loại. Rõ ràng gõ xstd::function<int (int, int)> thay vì sử dụng auto làm cho lỗi biến mất. Nhưng điều đó không cho phép tôi chuyển giá trị r vào Functor như tôi muốn. Tôi cũng không muốn loại bỏ bất kỳ loại an toàn nào bằng cách sử dụng một tham số mẫu khác cho loại hàm.

Những gì tôi thực sự không hiểu là tại sao mã trên thất bại trong việc biên dịch, nhưng mã dưới đây là tốt và hoạt động:

#include <functional> 

template <typename TReturn, typename TArgA, typename TArgB> 
void Functor(std::function<TReturn (TArgA, TArgB)> f) {} 

int main(int argc, char * argv[]) { 
    auto x = [] (int a, int b) { return a * b; }; 
    Functor<int, int, int> (x); 
    return 0; 
} 

Trả lời

10

Vấn đề là trình biên dịch không biết rằng bạn đã dự định int, int là toàn bộ TArgs và vì vậy hãy cố gắng suy ra phần còn lại của TArgs từ đối số f.

Ví dụ, điều này sẽ có giá trị:

Functor<int, int, int>(std::function<int(int, int, char, float)>{}); 
// TArgs := {int, int, [...]      char, float} 

Vì vậy, bạn cần phải chỉ thị cho trình biên dịch để không cố gắng để suy ra phần còn lại của TArgs.Ví dụ, bạn có thể viết:

(*Functor<int, int, int>)(x); 

Hoặc bạn có thể viết Functor với một tổ chức phi bị phân hủy chữ ký Sig:

template <Sig> 
void Functor(std::function<Sig> f) {} 

Hoặc bạn có thể quấn việc sử dụng TArgs trong tham số f trong một phi bối cảnh suy luận:

template <typename TReturn, typename ... TArgs> 
void Functor(std::function<std::conditional_t<false, void, TReturn (TArgs...)>> f) {} 
6

này thất bại:

#include <functional> 

template <typename TReturn, typename ... TArgs> 
void Functor(std::function<TReturn (TArgs...)> f) {} 

int main(int argc, char * argv[]) { 
    auto x = [] (int a, int b) { return a * b; }; 
    Functor<int, int, int>(x); 
    return 0; 
} 

vì bạn không chỉ định toàn bộ số TArgs...{int, int}. Những gì bạn đang làm là xác định rằng hai loại đầu tiên là {int, int}. Một cách hiệu quả, bằng cách cung cấp những ba loại, chúng tôi đã bật vấn đề khấu trừ vào:

template <typename ... TArgs> 
void Functor(std::function<int(int, int, TArgs...)> f) {} 

int main(int argc, char * argv[]) { 
    auto x = [] (int a, int b) { return a * b; }; 
    Functor(x); 
    return 0; 
} 

này không biên dịch vì một lambda không phải là một std::function (hoặc có nguồn gốc từ một), đó là lý do tương tự bạn không thể gọi điều này mà không cung cấp bất kỳ loại nào để bắt đầu.

Phiên bản không phiên bản không có vấn đề này, vì bạn đã cung cấp tất cả các loại.


Nhưng thực sự, những gì bạn muốn là:

template <typename F> 
void Functor(F) {} 

này không mất bất kỳ bạn an toàn loại. Nó đang sử dụng std::function để mất thông tin loại, vì mẫu lớp đó tồn tại để xóa.

0

Đây là giải pháp cho phép bạn gọi functor mà không chỉ định đối số mẫu của nó:

#include <functional> 
#include <type_traits> 

template <class T> struct Fun_trait {}; 

template <class T, class... Args, class Ret> 
struct Fun_trait<auto (T::*) (Args...) const -> Ret> 
{ 
    using F = std::function<auto (Args...) -> Ret>; 
}; 


template <class TReturn, class... TArgs> 
void functor(std::function<TReturn (TArgs...)> f) {} 

template <class F> 
std::void_t<decltype(&F::operator())> 
functor(F f) 
{ 
    return functor<typename Fun_trait<decltype(&F::operator())>::F>(f); 
}; 


int main(int argc, char * argv[]) 
{ 
    auto x = [] (int a, int b) { return a * b; }; 

    // nice and easy: 
    functor(x); 

    return 0; 
} 

Đây chỉ là bản nháp đầu tiên lười biếng để bạn bắt đầu. Bạn cần phải mở rộng nó để hỗ trợ chuyển tiếp và không const operator().


Nó hoạt động trong 2 giai đoạn:

1st chúng tôi có Fun_trait người - với nhiều loại phương pháp con trỏ (ví dụ các operator() của một lambda) - đã xác định một bí danh F cho các loại đối số bắt buộc std::function.

Tiếp theo chúng ta có một tình trạng quá tải của chức năng của bạn functor mà qua SFINAE với std::void_t đá trong chỉ cho functors với một tổ chức phi quá tải operator() (ví dụ như một lambda) và sau đó sử dụng các đặc điểm nêu trên gọi hàm chính functor với mẫu đối số đúng suy luận.

2

Rất hiếm khi có ý tưởng tốt để đúc một lambda thành std::function trong một template nếu bạn chỉ gọi nó. std::function là loại tẩy xoá, và xóa kiểu templated chỉ có ý nghĩa nếu bạn đang đi để "vượt qua nó" ở một nơi khác và/hoặc trả lại nó.

Trong mọi trường hợp, hãy thử này:

template <class Sig> 
void Functor(std::function<Sig> f) {} 

int main(int argc, char * argv[]) { 
    auto x = [] (int a, int b) { return a * b; }; 
    Functor<int(int, int)>(x); 
    return 0; 
} 

nhưng bạn nên thực sự chỉ làm

template <class F> 
void Functor(F f) {} 

đó là hoàn toàn loại an toàn.

Nếu bạn muốn kiểm tra loại sớm, bạn có thể viết

template<class Sig, class F> 
struct signature_compatible; 
template<class R, class...Args, class F> 
struct signature_compatible<R(Args...), F> : 
    std::is_consructible< R, std::result_of_t<F(Args...)>> 
{}; 

sau đó làm

template <class Sig, class F> 
void Functor(F f) { 
    static_assert(signature_compatible<Sig, F&>::value, "bad signature"); 
} 

nhưng chỉ khi bạn thực sự cần.

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