Tại sao một biến const đôi khi không được yêu cầu bắt buộc trong lambda?


76

Hãy xem xét ví dụ sau:

#include <cstdlib>

int main() {
    const int m = 42;
    [] { m; }(); // OK

    const int n = std::rand();
    [] { n; }(); // error: 'n' is not captured
}

Tại sao tôi cần chụp ntrong lambda thứ hai mà không phải mtrong lambda đầu tiên? Tôi đã kiểm tra phần 5.1.2 ( biểu thức Lambda ) trong tiêu chuẩn C ++ 14 nhưng tôi không thể tìm thấy lý do. Bạn có thể chỉ cho tôi một đoạn trong đó điều này được giải thích không?

Cập nhật: Tôi đã quan sát thấy hành vi này với cả GCC 6.3.1 và 7 (thân cây). Clang 4.0 và 5 (đường trục) không thành công với lỗi trong cả hai trường hợp ( variable 'm' cannot be implicitly captured in a lambda with no capture-default specified).


2
constexprvsconst
CinCout

1
ngaog chấp nhận cái đầu tiên nếu bạn đổi m;thànhm + 0;
MM

4
Cùng một lý do tại sao std::array<int,m>là OK và std::array<int,n>không.
n. 'đại từ' m.

3
Tôi ước rằng các biến không phải là constexprtrừ khi bạn chỉ định nó một cách rõ ràng. Nếu ai đó muốn constexprbiến thì người ta nên khai báo nó là một.
xinaiz

2
@BlackMoses: mkhông phải constexpr, đó là một biểu thức tích phân không đổi theo thời gian biên dịch ... đó là điều rất lâu trước khi constexprtừ khóa được mơ ước.
Ben Voigt

Câu trả lời:


56

Đối với lambda ở phạm vi khối, các biến đáp ứng các tiêu chí nhất định trong phạm vi tiếp cận có thể được sử dụng theo những cách hạn chế bên trong lambda, ngay cả khi chúng không được nắm bắt.

Nói một cách đại khái, phạm vi tiếp cận bao gồm bất kỳ biến cục bộ nào đối với hàm chứa lambda, sẽ nằm trong phạm vi tại thời điểm lambda được xác định. Vì vậy, điều này bao gồm mntrong các ví dụ trên.

Cụ thể là "tiêu chí nhất định" và "cách hạn chế" (kể từ C ++ 14):

  • Bên trong lambda, biến không được sử dụng odr , có nghĩa là nó không được trải qua bất kỳ hoạt động nào ngoại trừ:
    • xuất hiện dưới dạng một biểu thức giá trị bị loại bỏ ( m;là một trong những biểu thức này) hoặc
    • có giá trị của nó được truy xuất.
  • Biến phải là:
    • A const, không phải volatilesố nguyên hoặc enum có trình khởi tạo là một biểu thức hằng số , hoặc
    • A constexpr, không volatilebiến (hoặc một đối tượng con của nó)

Tài liệu tham khảo về C ++ 14: [expr.const] /2.7, [basic.def.odr] / 3 (câu đầu tiên), [expr.prim.lambda] / 12, [expr.prim.lambda] / 10.

Cơ sở lý luận cho các quy tắc này, như được đề xuất bởi các nhận xét / câu trả lời khác, là trình biên dịch cần có khả năng "tổng hợp" lambda no-capture như một hàm miễn phí độc lập với khối (vì những thứ như vậy có thể được chuyển đổi thành một con trỏ- chức năng); nó có thể làm điều này mặc dù tham chiếu đến biến nếu nó biết rằng biến sẽ luôn có cùng một giá trị hoặc nó có thể lặp lại quy trình lấy giá trị của biến độc lập với ngữ cảnh. Nhưng nó không thể làm điều này nếu biến có thể khác nhau theo thời gian hoặc nếu địa chỉ của biến là cần thiết.


Trong mã của bạn, nđã được khởi tạo bằng một biểu thức không phải là hằng số. Do đó, nkhông thể được sử dụng trong lambda mà không bị bắt.

mđược khởi tạo bởi một biểu thức hằng 42, vì vậy nó đáp ứng "tiêu chí nhất định". Biểu thức giá trị bị loại bỏ không sử dụng biểu thức, vì vậy m;có thể được sử dụng mà không mbị bắt. gcc là đúng.


Tôi sẽ nói rằng sự khác biệt giữa hai trình biên dịch là clang coi là m;sử dụng odr m, nhưng gcc thì không. Câu đầu tiên của [basic.def.odr] / 3 khá phức tạp:

Một biến xcó xuất hiện tên như là một biểu hiện khả năng đánh giá lại exODR-sử dụng bằng cách extrừ khi áp dụng việc chuyển đổi giá trị trái-to-rvalue đến xsản lượng một biểu thức hằng số mà không làm invoke bất kỳ chức năng không tầm thường, và nếu xlà một đối tượng, exlà một phần tử của tập hợp các kết quả tiềm năng của một biểu thức e, trong đó chuyển đổi giá trị thành giá trị được áp dụng ehoặc elà biểu thức giá trị bị loại bỏ.

nhưng khi đọc kỹ, nó đề cập cụ thể rằng một biểu thức giá trị bị loại bỏ không sử dụng biểu thức.

Phiên bản [basic.def.odr] của C ++ 11 ban đầu không bao gồm trường hợp biểu thức giá trị bị loại bỏ, vì vậy hành vi của clang sẽ đúng theo C ++ 11 đã xuất bản. Tuy nhiên, văn bản xuất hiện trong C ++ 14 đã được chấp nhận là Lỗi đối với C ++ 11 ( Vấn đề 712 ), vì vậy trình biên dịch nên cập nhật hành vi của họ ngay cả trong chế độ C ++ 11.


Tôi nghĩ [expr] / 11 có nghĩa là chuyển đổi lvalue-to-rvalue không được áp dụng trong trường hợp này.
cpplearner

@cpplearner Tôi đã không thấy điều đó sớm hơn nhưng tôi đồng ý với bạn rằng bạn đã chỉ ra điều đó, cảm ơn ... sẽ cập nhật câu trả lời của tôi
MM

Đọc lại, có vẻ như việc m;thực hiện chuyển đổi từ giá trị thành giá trị đến giá trị không liên quan. Định nghĩa của odr-use trong [basic.def.odr] / 3 cho biết "chuyển đổi lvalue-to-rvalue được áp dụng ehoặc elà biểu thức giá trị bị loại bỏ" và ở đây biểu thức mlà biểu thức giá trị bị loại bỏ, vì vậy cuối cùng biến mkhông được sử dụng odr.
cpplearner

@cpplearner Roger, tôi đã đọc nhầm "biểu thức giá trị bị loại bỏ" thành "biểu thức không được đánh giá"
MM

34

Bởi vì nó là một biểu thức không đổi, trình biên dịch xử lý như thể nó [] { 42; }();

Quy tắc trong [ expr.prim.lambda ] là:

Nếu một biểu thức lambda hoặc một mô tả của mẫu toán tử lệnh gọi hàm của một lambda odr chung chung sử dụng (3.2) này hoặc một biến có thời lượng lưu trữ tự động từ phạm vi tiếp cận của nó, thì thực thể đó sẽ được ghi lại bởi biểu thức lambda.

Đây là trích dẫn từ tiêu chuẩn [ basic.def.odr ]:

Biến x có tên xuất hiện dưới dạng biểu thức được đánh giá tiềm năng ex được sử dụng odr trừ khi áp dụng chuyển đổi giá trị thành giá trị cho x mang lại biểu thức hằng số (...) hoặc e là biểu thức giá trị bị loại bỏ.

(Đã loại bỏ phần không quá quan trọng để giữ cho nó ngắn gọn)

Hiểu đơn giản của tôi là: trình biên dịch biết rằng mnó không đổi tại thời điểm biên dịch, trong khi nsẽ thay đổi tại thời điểm chạy và do đó nphải được ghi lại. nsẽ được sử dụng odr, bởi vì bạn phải thực sự xem xét những gì bên trong nlúc chạy. Nói cách khác, thực tế là định nghĩa "chỉ có thể có một" nlà phù hợp.

Đây là từ một bình luận của MM:

m là một biểu thức hằng vì nó là một biến tự động const với bộ khởi tạo biểu thức không đổi, nhưng n không phải là một biểu thức hằng vì bộ khởi tạo của nó không phải là một biểu thức hằng. Điều này được đề cập trong [expr.const] /2.7. Biểu thức hằng không được sử dụng ODR, theo câu đầu tiên của [basic.def.odr] / 3

Xem ở đây để có bản demo .


5
Sẽ rất tốt nếu bạn giải thích chi tiết hơn một chút tại sao n; sử dụng odr n , nhưng m;không sử dụng odr m
MM

1
Tôi không hiểu đoạn đó có liên quan như thế nào vì nó nói rằng thực thể sẽ được biểu thức lambda nắm bắt. Trong lambda đầu tiên, không có bản chụp, nhưng GCC đã biên dịch nó.
s3rvac

@ s3rvac Xem bình luận của MM Chìa khóa ở đây là trong một trường hợp có cách sử dụng odr, nhưng không có trong trường hợp khác.
Ilya Popov

@IlyaPopov Các biến được sử dụng theo cách giống nhau trong cả hai lambda, vì vậy tôi không hiểu tại sao nên sử dụng odr trong một lambda chứ không phải trong lambda khác.
s3rvac

2
@ s3rvac đoạn có nội dung " Nếu [các điều kiện được đáp ứng], thực thể đó sẽ bị bắt". Các điều kiện được đáp ứng cho nnhưng không m.
MM

2

CHỈNH SỬA: Phiên bản trước của câu trả lời của tôi là sai. Người mới bắt đầu là chính xác, đây là báo giá tiêu chuẩn có liên quan:

[basic.def.odr]

  1. Một biến x có tên xuất hiện dưới dạng biểu thức được đánh giá tiềm năng ex được odr sử dụng bởi ex trừ khi áp dụng chuyển đổi giá trị thành giá trị r cho x mang lại biểu thức hằng số không gọi bất kỳ hàm không tầm thường nào và nếu x là một đối tượng , ex là một phần tử của tập hợp các kết quả tiềm năng của biểu thức e, trong đó chuyển đổi giá trị thành giá trị được áp dụng cho e hoặc e là biểu thức giá trị bị loại bỏ. ...

mlà một biểu thức hằng, nó không được sử dụng odr và do đó không cần phải nắm bắt.

Có vẻ như hành vi kêu vang không tuân thủ tiêu chuẩn.


Cái đầu tiên thực sự hoạt động, hãy thử nó tại đây: ideone.com/o8WIO1
Beginner

Tôi tin rằng điều này là ok, xem báo giá từ tiêu chuẩn. Làm thế nào để bạn nhìn thấy nó?
Bắt đầu

@Beginner trích dẫn đó thực sự xác nhận câu trả lời của tôi. "sẽ" áp dụng hạn chế đối với chương trình. Nó có nghĩa là biến phải được bắt. Vì nó không được nắm bắt (không rõ ràng hoặc không hoàn toàn bằng cách sử dụng default-capture), chương trình vi phạm quy tắc đó.
eerorika

Các biến tự động từ phạm vi tiếp cận là các biểu thức không đổi, không cần phải được ghi lại
MM

1
@MM đó là thú vị. Bạn có thể tìm thấy quy tắc nói như vậy không?
eerorika
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.