Có thể tìm ra loại tham số và loại trả về của lambda?


135

Đưa ra một lambda, có thể tìm ra loại tham số và loại trả về không? Nếu có, làm thế nào?

Về cơ bản, tôi muốn lambda_traitscó thể được sử dụng theo những cách sau:

auto lambda = [](int i) { return long(i*10); };

lambda_traits<decltype(lambda)>::param_type  i; //i should be int
lambda_traits<decltype(lambda)>::return_type l; //l should be long

Động lực đằng sau là tôi muốn sử dụng lambda_traitstrong một mẫu hàm chấp nhận lambda làm đối số và tôi cần biết loại tham số và loại trả về bên trong hàm:

template<typename TLambda>
void f(TLambda lambda)
{
   typedef typename lambda_traits<TLambda>::param_type  P;
   typedef typename lambda_traits<TLambda>::return_type R;

   std::function<R(P)> fun = lambda; //I want to do this!
   //...
}

Hiện tại, chúng ta có thể giả sử rằng lambda có chính xác một đối số.

Ban đầu, tôi đã cố gắng làm việc với std::function:

template<typename T>
A<T> f(std::function<bool(T)> fun)
{
   return A<T>(fun);
}

f([](int){return true;}); //error

Nhưng nó rõ ràng sẽ cho lỗi. Vì vậy, tôi đã thay đổi nó thành TLambdaphiên bản của mẫu hàm và muốn xây dựng std::functionđối tượng bên trong hàm (như được hiển thị ở trên).


Nếu bạn biết loại tham số thì có thể sử dụng loại này để tìm ra kiểu trả về. Tôi không biết làm thế nào để tìm ra loại tham số mặc dù.
Mankude

Có phải giả định rằng hàm có đối số duy nhất?
iammilind

1
"Kiểu tham số" Nhưng hàm lambda tùy ý không có kiểu tham số. Nó có thể mất bất kỳ số lượng tham số. Vì vậy, bất kỳ lớp đặc điểm nào sẽ phải được thiết kế để truy vấn các tham số theo chỉ số vị trí.
Nicol Bolas

@iammilind: Vâng. Hiện tại, chúng ta có thể giả định rằng.
Nawaz

@NicolBolas: Hiện tại, chúng ta có thể giả sử rằng lambda có chính xác một đối số.
Nawaz

Câu trả lời:


160

Thật buồn cười, tôi vừa viết một function_traitstriển khai dựa trên Chuyên về một mẫu trên lambda trong C ++ 0x có thể đưa ra các loại tham số. Thủ thuật, như được mô tả trong câu trả lời trong câu hỏi đó, là sử dụng lambda . decltypeoperator()

template <typename T>
struct function_traits
    : public function_traits<decltype(&T::operator())>
{};
// For generic types, directly use the result of the signature of its 'operator()'

template <typename ClassType, typename ReturnType, typename... Args>
struct function_traits<ReturnType(ClassType::*)(Args...) const>
// we specialize for pointers to member function
{
    enum { arity = sizeof...(Args) };
    // arity is the number of arguments.

    typedef ReturnType result_type;

    template <size_t i>
    struct arg
    {
        typedef typename std::tuple_element<i, std::tuple<Args...>>::type type;
        // the i-th argument is equivalent to the i-th tuple element of a tuple
        // composed of those arguments.
    };
};

// test code below:
int main()
{
    auto lambda = [](int i) { return long(i*10); };

    typedef function_traits<decltype(lambda)> traits;

    static_assert(std::is_same<long, traits::result_type>::value, "err");
    static_assert(std::is_same<int, traits::arg<0>::type>::value, "err");

    return 0;
}

Lưu ý rằng giải pháp này không hoạt động cho lambda chung như thế nào [](auto x) {}.


Heh, tôi chỉ đang viết này. Không nghĩ về tuple_elementmặc dù, cảm ơn.
GManNickG

@GMan: Nếu cách tiếp cận của bạn không chính xác như thế này, xin vui lòng gửi nó sau đó. Tôi sẽ thử nghiệm giải pháp này.
Nawaz

3
Một đặc điểm hoàn chỉnh cũng sẽ sử dụng một chuyên ngành cho không const, cho những lambda đã khai báo mutable( []() mutable -> T { ... }).
Luc Danton

1
@Andry đó là một vấn đề cơ bản với các đối tượng chức năng có (có khả năng) nhiều tình trạng quá tải operator()không xảy ra với việc triển khai này. autokhông phải là một loại, vì vậy nó không bao giờ có thể là câu trả lời chotraits::template arg<0>::type
Caleth

1
@helmesjo sf.net/p/tacklelib/tacklelib/HEAD/tree/trunk/include/tacklelib/... Là một giải pháp cho các liên kết bị hỏng: cố gắng tìm kiếm từ gốc, Luke.
Andry

11

Mặc dù tôi không chắc đây là tiêu chuẩn tuân thủ nghiêm ngặt, ideone đã biên dịch mã sau:

template< class > struct mem_type;

template< class C, class T > struct mem_type< T C::* > {
  typedef T type;
};

template< class T > struct lambda_func_type {
  typedef typename mem_type< decltype( &T::operator() ) >::type type;
};

int main() {
  auto l = [](int i) { return long(i); };
  typedef lambda_func_type< decltype(l) >::type T;
  static_assert( std::is_same< T, long( int )const >::value, "" );
}

Tuy nhiên, điều này chỉ cung cấp loại chức năng, do đó, các loại kết quả và tham số phải được trích xuất từ ​​nó. Nếu bạn có thể sử dụng boost::function_traits, result_typearg1_type sẽ đáp ứng mục đích. Vì ideone dường như không cung cấp tăng cường trong chế độ C ++ 11, tôi không thể đăng mã thực tế, xin lỗi.


1
Tôi nghĩ rằng, đó là một khởi đầu tốt. +1 cho điều đó. Bây giờ chúng ta cần làm việc với loại chức năng để trích xuất thông tin cần thiết. (Hiện tại tôi không muốn sử dụng Boost, vì tôi muốn tìm hiểu nội dung).
Nawaz

6

Phương pháp chuyên môn hóa được hiển thị trong câu trả lời của @KennyTM có thể được mở rộng để bao quát tất cả các trường hợp, bao gồm cả lambdas biến đổi và đột biến:

template <typename T>
struct closure_traits : closure_traits<decltype(&T::operator())> {};

#define REM_CTOR(...) __VA_ARGS__
#define SPEC(cv, var, is_var)                                              \
template <typename C, typename R, typename... Args>                        \
struct closure_traits<R (C::*) (Args... REM_CTOR var) cv>                  \
{                                                                          \
    using arity = std::integral_constant<std::size_t, sizeof...(Args) >;   \
    using is_variadic = std::integral_constant<bool, is_var>;              \
    using is_const    = std::is_const<int cv>;                             \
                                                                           \
    using result_type = R;                                                 \
                                                                           \
    template <std::size_t i>                                               \
    using arg = typename std::tuple_element<i, std::tuple<Args...>>::type; \
};

SPEC(const, (,...), 1)
SPEC(const, (), 0)
SPEC(, (,...), 1)
SPEC(, (), 0)

Bản demo .

Lưu ý rằng arity không được điều chỉnh cho operator()s. Thay vào đó người ta cũng có thể xem xét is_variadic.


1

Câu trả lời được cung cấp bởi @KennyTMs hoạt động rất tốt, tuy nhiên nếu lambda không có tham số, sử dụng chỉ số arg <0> sẽ không biên dịch. Nếu bất cứ ai khác gặp phải vấn đề này, tôi có một giải pháp đơn giản (đơn giản hơn là sử dụng các giải pháp liên quan đến SFINAE, đó là).

Chỉ cần thêm khoảng trống vào cuối bộ dữ liệu trong cấu trúc arg sau các kiểu đối số biến đổi. I E

template <size_t i>
    struct arg
    {
        typedef typename std::tuple_element<i, std::tuple<Args...,void>>::type type;
    };

do mức độ không phụ thuộc vào số lượng tham số mẫu thực tế, nên thực tế sẽ không chính xác và nếu là 0 thì ít nhất arg <0> vẫn sẽ tồn tại và bạn có thể làm gì với nó. Nếu bạn đã có kế hoạch không vượt quá chỉ mục arg<arity-1>thì nó không nên can thiệp vào việc triển khai hiện tại của bạn.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.