Chức năng lambda có thể được templated?


230

Trong C ++ 11, có cách nào để tạo mẫu hàm lambda không? Hoặc nó vốn đã quá cụ thể để được templated?

Tôi hiểu rằng tôi có thể định nghĩa một lớp / functor cổ điển thay thế, nhưng câu hỏi giống như: ngôn ngữ có cho phép tạo các hàm lambda không?


Có một trường hợp sử dụng trong đó một mẫu lambda sẽ hữu ích?
James McNellis

7
James: Bạn có thể xây dựng một hàm để lặp qua một tuple (Không nhất thiết phải hữu ích).
Joe D

Tôi nghĩ về ý tưởng trong khi đọc một cuộc phỏng vấn của Stroustrup nói về sự phức tạp của siêu mẫu là một vấn đề. Nếu nó được cho phép, tôi đã tưởng tượng ninja code-fu có thể được phát minh bởi các lập trình viên quá thông minh chơi với sự kết hợp các tính năng này ...
Klaim

Câu trả lời:


181

CẬP NHẬT 2018: C ++ 20 sẽ đi kèm với lambdas templated và khái niệm hóa. Các tính năng đã được tích hợp vào dự thảo tiêu chuẩn.


CẬP NHẬT 2014: C ++ 14 đã được phát hành trong năm nay và hiện cung cấp lambdas đa hình với cú pháp giống như trong ví dụ này. Một số trình biên dịch chính đã thực hiện nó.


Tại nó đứng (trong C ++ 11), đáng buồn là không. Lambdas đa hình sẽ là tuyệt vời về sự linh hoạt và sức mạnh.

Lý do ban đầu họ cuối cùng là đơn hình là vì các khái niệm. Các khái niệm làm cho tình huống mã này trở nên khó khăn:

template <Constraint T>
void foo(T x)
{
    auto bar = [](auto x){}; // imaginary syntax
}

Trong một mẫu bị ràng buộc, bạn chỉ có thể gọi các mẫu bị ràng buộc khác. (Nếu không, các ràng buộc không thể được kiểm tra.) Có thể foogọi bar(x)? Lambda có những ràng buộc nào (tham số cho nó chỉ là một mẫu)?

Các khái niệm chưa sẵn sàng để giải quyết vấn đề này; nó sẽ yêu cầu nhiều thứ hơn như late_check(trong đó khái niệm không được kiểm tra cho đến khi được gọi) và nội dung. Đơn giản hơn chỉ là thả tất cả và dính vào lambdas đơn hình.

Tuy nhiên, với việc loại bỏ các khái niệm khỏi C ++ 0x, lambdas đa hình lại trở thành một đề xuất đơn giản. Tuy nhiên, tôi không thể tìm thấy bất kỳ đề xuất nào cho nó. :


5
Đơn giản ... ngoại trừ mong muốn giới thiệu lại các khái niệm và tránh các tính năng khiến chúng trở nên phức tạp.

6
Tôi nghĩ rằng tôi muốn có lambdas đa hình hơn các khái niệm. Tôi không hiểu làm thế nào ví dụ thúc đẩy bất cứ điều gì; bạn chỉ có thể cấm nó là một lỗi và yêu cầu lambda phải là đơn hình [] (T x) {} hoặc mẫu bị ràng buộc [] mẫu <Ràng buộc T> (T x) {}, có thể được xác minh tĩnh để khớp. Có một số lý do tại sao điều này là không thể?
DrPizza

13
Bạn không phải lựa chọn giữa các khái niệm và lambdas đa hình: cpp-next.com/archive/2011/12/a-breakENC-for-con accept
Dave Abrahams

3
Đây là đề nghị cho lambdas đa hình: open-std.org/jtc1/sc22/wg21/docs/papers/2012/n3418.pdf và thực hiện đồ chơi trong kêu vang: faisalv.github.com/clang-glambda
Radif Sharafullin

18
Lambdas đa hình sẽ có trong C ++ 14, ít nhất là chúng nằm trong Dự thảo cộng đồng vào lúc này :)
Arne Mertz

37

C ++ 11 lambdas không thể được tạo khuôn mẫu như đã nêu trong các câu trả lời khác nhưng decltype()dường như có ích khi sử dụng lambda trong một lớp hoặc chức năng templated.

#include <iostream>
#include <string>

using namespace std;

template<typename T>
void boring_template_fn(T t){
    auto identity = [](decltype(t) t){ return t;};
    std::cout << identity(t) << std::endl;
}

int main(int argc, char *argv[]) {
    std::string s("My string");
    boring_template_fn(s);
    boring_template_fn(1024);
    boring_template_fn(true);
}

Bản in:

My string
1024
1

Tôi đã tìm thấy kỹ thuật này hữu ích khi làm việc với mã templated nhưng nhận ra nó vẫn có nghĩa là bản thân lambdas không thể được tạo khuôn.


26
Tsẽ làm việc tốt thay cho decltype(t)trong ví dụ này.
2023370

26

Trong C ++ 11, các chức năng lambda không thể được tạo khuôn mẫu, nhưng trong phiên bản tiếp theo của Tiêu chuẩn ISO C ++ (thường được gọi là C ++ 14), tính năng này sẽ được giới thiệu. [Nguồn]

Ví dụ sử dụng:

auto get_container_size = [] (auto container) { return container.size(); };

Lưu ý rằng mặc dù cú pháp sử dụng từ khóa auto, loại trừ sẽ không sử dụng quy tắc autokhấu trừ loại, mà thay vào đó sử dụng quy tắc khấu trừ đối số mẫu. Cũng xem đề xuất cho các biểu thức lambda chung (và bản cập nhật cho điều này).


5
Các quy tắc autokhấu trừ loại được xác định cụ thể giống như quy tắc khấu trừ templatehàm.
gạch dưới

10

Tôi biết rằng câu hỏi này là về C ++ 11. Tuy nhiên, đối với những người đã googled và hạ cánh trên trang này, lambdas templated hiện được hỗ trợ trong C ++ 14 và đi với tên Generic Lambdas.

[thông tin] Hầu hết các trình biên dịch phổ biến đều hỗ trợ tính năng này ngay bây giờ. Hỗ trợ Microsoft Visual Studio 2015. Clang hỗ trợ. GCC hỗ trợ.


6

Tôi tự hỏi những gì về điều này:

template <class something>
inline std::function<void()> templateLamda() {
  return [](){ std::cout << something.memberfunc() };
}

Tôi đã sử dụng mã tương tự như thế này, để tạo một mẫu và tự hỏi liệu trình biên dịch sẽ tối ưu hóa chức năng "gói" ra.


2
Trình biên dịch gì? Đã làm nó?
NicoBerrog lỗi


3

Có một phần mở rộng gcc cho phép các mẫu lambda :

// create the widgets and set the label
base::for_each(_widgets, [] <typename Key_T, typename Widget_T>
                         (boost::fusion::pair<Key_T, Widget_T*>& pair) -> void {
                             pair.second = new Widget_T();
                             pair.second->set_label_str(Key_T::label);
                          }
              );

nơi _widgetslà mộtstd::tuple< fusion::pair<Key_T, Widget_T>... >


FWIW, điều này đã trở thành cú pháp tiêu chuẩn trong C ++ 20.
LF

2

Tôi đã chơi với bản clang mới nhất được version 5.0.1biên dịch với -std=c++17cờ và bây giờ có một số hỗ trợ tốt cho các tham số loại tự động cho lambdas:

#include <iostream>
#include <vector>
#include <stdexcept>

int main() {
    auto slice = [](auto input, int beg, int end) {
        using T = decltype(input);
        const auto size = input.size();
        if (beg > size || end > size || beg < 0 || end < 0) {
            throw std::out_of_range("beg/end must be between [0, input.size())");
        }
        if (beg > end) {
            throw std::invalid_argument("beg must be less than end");
        }
        return T(input.begin() + beg, input.begin() + end);
    };
    auto v = std::vector<int> { 1,2,3,4,5 };
    for (auto e : slice(v, 1, 4)) {
        std::cout << e << " ";
    }
    std::cout << std::endl;
}

1

Đây là một giải pháp liên quan đến việc bọc lamba trong một cấu trúc:

template <typename T>                                                   
struct LamT                                                             
{                                                                       
   static void Go()                                                     
   {                                                                    
      auto lam = []()                                                   
      {                                                                 
         T var;                                                         
         std::cout << "lam, type = " << typeid(var).name() << std::endl;
      };                                                                

      lam();                                                            
   }                                                                    
};   

Để sử dụng làm:

LamT<int>::Go();  
LamT<char>::Go(); 
#This prints 
lam, type = i
lam, type = c

Vấn đề chính với điều này (ngoài việc gõ thêm), bạn không thể nhúng định nghĩa cấu trúc này bên trong một phương thức khác hoặc bạn nhận được (gcc 4.9)

error: a template declaration cannot appear at block scope

Tôi cũng đã thử làm điều này:

template <typename T> using LamdaT = decltype(                          
   [](void)                                                          
   {                                                                 
       std::cout << "LambT type = " << typeid(T).name() << std::endl;  
   });

Với hy vọng tôi có thể sử dụng nó như thế này:

LamdaT<int>();      
LamdaT<char>();

Nhưng tôi gặp lỗi trình biên dịch:

error: lambda-expression in unevaluated context

Vì vậy, điều này không hoạt động ... nhưng ngay cả khi nó đã biên dịch, nó sẽ bị hạn chế sử dụng vì chúng ta vẫn phải đặt "sử dụng LamdaT" trong phạm vi tệp (vì đó là một mẫu) nhằm đánh bại mục đích của thịt cừu


1

Tôi không chắc tại sao không ai khác đề xuất điều này, nhưng bạn có thể viết một hàm templated trả về các hàm lambda. Sau đây đã giải quyết vấn đề của tôi, lý do tôi đến trang này:

template <typename DATUM>
std::function<double(DATUM)> makeUnweighted() {
  return [](DATUM datum){return 1.0;};
}

Bây giờ bất cứ khi nào tôi muốn một hàm có một loại đối số nhất định (ví dụ std::string), tôi chỉ cần nói

auto f = makeUnweighted<std::string>()

và bây giờ f("any string")trở lại 1.0.

Đó là một ví dụ về những gì tôi muốn nói bởi "hàm lambda templated." (Trường hợp cụ thể này được sử dụng để tự động cung cấp chức năng cân trơ khi ai đó không muốn cân dữ liệu của họ, bất kể dữ liệu của họ có thể là gì.)


2
Điều này chỉ hoạt động nếu bạn biết loại đối số cho lambda trước khi tạo lambda, trong trường hợp đó bạn chỉ có thể sử dụng lambda với loại đối số cụ thể làm đối số. Điểm của lambda đa hình là cung cấp công việc được thực hiện trên một loại đối số mà bạn không bao giờ biết khi bạn viết mã công việc. Về cơ bản, điều này là hoàn toàn khác nhau, đó là lý do tại sao nó không được đề xuất.
Klaim

À, đúng rồi, hiểu rồi. Tôi đã không nghĩ về trường hợp sử dụng đó --- Tôi nghĩ rằng các hàm lambda là những thứ đang hoạt động và loại đa hình đó là một thứ gì đó trong một thư viện đa năng. Tôi đã viết một thư viện templated cần chấp nhận các chức năng lambda của người dùng thuộc bất kỳ loại nào và cũng cung cấp mặc định đúng loại.
Jim Pivarski
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.