Giải quyết tình trạng quá tải không rõ ràng trên con trỏ hàm và hàm std :: cho lambda bằng cách sử dụng +


93

Trong đoạn mã sau, lệnh gọi đầu tiên fookhông rõ ràng và do đó không biên dịch được.

Thứ hai, với phần được thêm vào +trước lambda, giải quyết tình trạng quá tải con trỏ hàm.

#include <functional>

void foo(std::function<void()> f) { f(); }
void foo(void (*f)()) { f(); }

int main ()
{
    foo(  [](){} ); // ambiguous
    foo( +[](){} ); // not ambiguous (calls the function pointer overload)
}

Là gì +chú làm gì ở đây?

Câu trả lời:


98

Trong +biểu thức +[](){}là toán +tử một ngôi. Nó được định nghĩa như sau trong [expr.unary.op] / 7:

Toán hạng của toán +tử một ngôi phải có kiểu số học, kiểu liệt kê không ghi hoặc kiểu con trỏ và kết quả là giá trị của đối số.

Lambda không thuộc loại số học, v.v., nhưng nó có thể được chuyển đổi:

[expr.prim.lambda] / 3

Kiểu của lambda-expression [...] là một kiểu lớp không liên hợp duy nhất, không được đặt tên - được gọi là kiểu đóng - có các thuộc tính được mô tả bên dưới.

[expr.prim.lambda] / 6

Kiểu đóng cửa cho một lambda thể hiện không có lambda-chụp có một publicphi virtualphi explicit constchức năng chuyển đổi để con trỏ đến chức năng có các loại tham số và lợi nhuận tương tự như hàm operator gọi kiểu đóng của. Giá trị được trả về bởi hàm chuyển đổi này sẽ là địa chỉ của một hàm mà khi được gọi ra, nó có tác dụng giống như việc gọi toán tử gọi hàm của kiểu đóng.

Do đó, hàm đơn nguyên +buộc chuyển đổi sang kiểu con trỏ hàm, dành cho lambda này void (*)(). Do đó, kiểu của biểu thức +[](){}là kiểu con trỏ hàm này void (*)().

Quá tải thứ hai void foo(void (*f)())trở thành Đối sánh chính xác trong xếp hạng để giải quyết quá tải và do đó được chọn rõ ràng (vì quá tải đầu tiên KHÔNG phải là Đối sánh chính xác).


Lambda [](){}có thể được chuyển đổi thành std::function<void()>thông qua ctor mẫu không rõ ràng std::function, lấy bất kỳ kiểu nào đáp ứng các yêu cầu CallableCopyConstructible.

Lambda cũng có thể được chuyển đổi thành void (*)()thông qua hàm chuyển đổi của kiểu đóng (xem ở trên).

Cả hai đều là chuỗi chuyển đổi do người dùng xác định và có cùng thứ hạng. Đó là lý do tại sao giải quyết quá tải không thành công trong ví dụ đầu tiên do không rõ ràng.


Theo Cassio Neri, được hỗ trợ bởi một lập luận của Daniel Krügler, +thủ thuật đơn lẻ này nên được chỉ định hành vi, tức là bạn có thể dựa vào nó (xem thảo luận trong phần bình luận).

Tuy nhiên, tôi khuyên bạn nên sử dụng kiểu ép kiểu rõ ràng cho kiểu con trỏ hàm nếu bạn muốn tránh sự mơ hồ: bạn không cần phải hỏi SO là gì và tại sao nó hoạt động;)


3
Con trỏ hàm thành viên @Fred AFAIK không thể được chuyển đổi thành con trỏ hàm không phải thành viên, chứ đừng nói đến giá trị hàm. Bạn có thể liên kết một hàm thành viên thông qua std::bindmột std::functionđối tượng có thể được gọi tương tự như một hàm lvalue.
dyp

2
@DyP: Tôi tin rằng chúng ta có thể dựa vào sự khôn lanh. Thật vậy, giả sử rằng một triển khai thêm operator +()vào một kiểu đóng không trạng thái. Giả sử rằng toán tử này trả về một cái gì đó khác với con trỏ đến hàm mà kiểu đóng chuyển đổi thành. Sau đó, điều này sẽ thay đổi hành vi có thể quan sát được của một chương trình vi phạm 5.1.2 / 3. Vui lòng cho tôi biết nếu bạn đồng ý với lý do này.
Cassio Neri,

2
@CassioNeri Vâng, đó là điểm mà tôi không chắc. Tôi đồng ý rằng hành vi có thể quan sát được có thể thay đổi khi thêm một operator +, nhưng điều này được so sánh với tình huống không có operator +để bắt đầu. Nhưng nó không được chỉ định rằng kiểu đóng sẽ không có operator +quá tải. "Việc triển khai có thể xác định kiểu đóng khác với những gì được mô tả bên dưới miễn là điều này không làm thay đổi hành vi có thể quan sát được của chương trình ngoài [...]" nhưng IMO thêm toán tử không thay đổi kiểu đóng thành một thứ gì đó khác với được "mô tả bên dưới".
dyp

3
@DyP: Tình huống không có operator +()chính xác cái được mô tả theo tiêu chuẩn. Tiêu chuẩn cho phép một triển khai thực hiện một cái gì đó khác với những gì được chỉ định. Ví dụ, thêm operator +(). Tuy nhiên, nếu sự khác biệt này có thể quan sát được bởi một chương trình, thì đó là bất hợp pháp. Một lần tôi đã hỏi trong comp.lang.c ++. Đã được kiểm duyệt nếu một loại bao đóng có thể thêm một typedef cho result_typevà cái khác được typedefsyêu cầu để làm cho chúng có thể thích nghi (ví dụ: bằng cách std::not1). Tôi đã nói rằng nó không thể vì điều này có thể quan sát được. Tôi sẽ cố gắng tìm liên kết.
Cassio Neri,

6
VS15 cung cấp cho bạn lỗi thú vị này: test.cpp (543): lỗi C2593: 'operator +' là không rõ ràng t \ test.cpp (543): lưu ý: có thể là 'toán tử C ++ tích hợp + (void (__cdecl *) (void )) 't \ test.cpp (543): ghi chú: hoặc' toán tử C ++ tích hợp sẵn + (void (__stdcall *) (void)) 't \ test.cpp (543): ghi chú: hoặc' toán tử C ++ tích hợp sẵn + (void (__fastcall *) (void)) 't \ test.cpp (543): note: hoặc' built-in C ++ operator + (void (__vectorcall *) (void)) 't \ test.cpp (543): note : trong khi cố gắng để phù hợp với danh sách đối số '(wmain :: <lambda_d983319760d11be517b3d48b95b3fe58>) test.cpp (543): lỗi C2088: '+': bất hợp pháp cho các lớp học
Ed Lambert
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.