Lambda chụp và tham số có cùng tên - ai làm bóng cho người khác? (tiếng kêu vs gcc)


125
auto foo = "You're using g++!";
auto compiler_detector = [foo](auto foo) { std::puts(foo); };
compiler_detector("You're using clang++!");
  • clang ++ 3.6.0 và mới hơn in ra "Bạn đang sử dụng clang ++!" và cảnh báo về việc bắt giữ foo không được sử dụng.

  • g ++ 4.9.0 và mới hơn in ra "Bạn đang sử dụng g ++!" và cảnh báo về thông số foo không được sử dụng.

Trình biên dịch nào chính xác hơn theo tiêu chuẩn C ++ ở đây?

ví dụ về hộp đựng đũa


1
Việc dán mã từ Wandbox đến đây (dường như họ đã quên nút chia sẻ) khiến cho có vẻ như VS2015 (?) Đồng ý với tiếng kêu cảnh báo C4458: tuyên bố của 'foo' ẩn thành viên lớp .
nwp

12
Ví dụ tuyệt vời ..
deviantfan 7/2/2017

4
Lambda có một loại với một toán tử gọi hàm mẫu, do đó logic sẽ khiến tôi nói rằng tham số này sẽ che đi biến đã bắt như thể trong struct Lambda { template<typename T> void operator()(T foo) const { /* ... */ } private: decltype(outer_foo) foo{outer_foo}; }.
skypjack

2
@nwp VS sai, các thành viên dữ liệu của lambda không được đặt tên và do đó không thể bị che khuất. Tiêu chuẩn cho biết "quyền truy cập vào một thực thể bị bắt được chuyển đổi để truy cập vào thành viên dữ liệu tương ứng", điều này khiến chúng ta đứng ở vị trí vuông.
n. 'đại từ' m.

10
Tôi hy vọng phiên bản clang là chính xác - nó sẽ đột phá nếu một cái gì đó bên ngoài chức năng che mờ tham số chức năng, thay vì ngược lại!
MM

Câu trả lời:


65

Cập nhật: như lời hứa của chủ tịch Core trong trích dẫn dưới cùng, mã hiện không được định dạng :

Nếu một định danh trong một đơn giản chụp xuất hiện như là declarator-id của một tham số của lambda-declarator 's tham số-khai-khoản , chương trình là vô hình thành.


Có một vài vấn đề liên quan đến việc tra cứu tên trong lambdas một thời gian trước đây. Họ đã được giải quyết bằng N2927 :

Từ ngữ mới không còn phụ thuộc vào việc tra cứu để sử dụng lại các thực thể bị bắt. Nó phủ nhận rõ ràng hơn những diễn giải rằng câu lệnh ghép của lambda được xử lý trong hai lần hoặc bất kỳ tên nào trong câu lệnh ghép đó có thể giải quyết cho một thành viên của kiểu đóng.

Tra cứu luôn được thực hiện trong ngữ cảnh của biểu thức lambda , không bao giờ "sau" chuyển đổi thành thân hàm thành viên của kiểu đóng. Xem [expr.prim.lambda] / 8 :

Câu lệnh ghép của biểu thức lambda mang lại thân hàm ([dcl.fct.def]) của toán tử gọi hàm, nhưng với mục đích tra cứu tên, [đùa], câu lệnh ghép được xem xét trong ngữ cảnh của các lambda-biểu . [ Ví dụ :

struct S1 {
  int x, y;
  int operator()(int);
  void f() {
    [=]()->int {
      return operator()(this->x+y);  // equivalent to: S1::operator()(this->x+(*this).y)
                                     // and this has type S1*
    }; 
  }
};

- ví dụ cuối ]

(Ví dụ cũng làm rõ rằng việc tra cứu không bằng cách nào đó xem xét thành viên chụp được tạo ra của kiểu đóng.)

Tên fookhông được khai báo lại trong bản chụp; nó được khai báo trong khối kèm theo biểu thức lambda. Tham số foođược khai báo trong một khối được lồng trong khối bên ngoài đó (xem [basic.scope.block] / 2 , cũng đề cập rõ ràng đến các tham số lambda). Thứ tự tra cứu rõ ràng từ khối bên trong đến khối bên ngoài . Do đó tham số nên được chọn, nghĩa là Clang đúng.

Nếu bạn định chụp ảnh bắt đầu, tức là foo = ""thay vì foo, câu trả lời sẽ không rõ ràng. Điều này là do việc chụp hiện thực sự gây ra một tuyên bố có "khối" không được đưa ra. Tôi nhắn tin cho cái ghế cốt lõi này, ai trả lời

Đây là vấn đề 2211 (một danh sách các vấn đề mới sẽ sớm xuất hiện trên trang web open-std.org, thật không may, chỉ với những người giữ chỗ cho một số vấn đề, trong đó đây là một vấn đề; Tôi đang nỗ lực để lấp đầy những khoảng trống đó trước Kona họp vào cuối tháng). CWG đã thảo luận về vấn đề này trong buổi hội thảo từ xa vào tháng 1 của chúng tôi và hướng đi là làm cho chương trình không thành hình nếu tên chụp cũng là tên tham số.


Không có gì để tôi tách ra ở đây :) Một cách chụp đơn giản không tuyên bố gì cả, vì vậy kết quả chính xác của việc tra cứu tên là khá rõ ràng (BTW, GCC làm cho đúng nếu bạn sử dụng mặc định chụp thay vì chụp rõ ràng). init-Capture s có phần phức tạp hơn.
TC

1
@TC tôi đồng ý. Tôi đã nộp một vấn đề cốt lõi, nhưng dường như điều này đã được thảo luận, xem câu trả lời được chỉnh sửa.
Columbo

6

Tôi đang cố gắng tập hợp một vài bình luận cho câu hỏi để cho bạn một câu trả lời có ý nghĩa.
Trước hết, lưu ý rằng:

  • Các thành viên dữ liệu không tĩnh được khai báo cho lambda cho mỗi biến được sao chép
  • Trong trường hợp cụ thể, lambda có kiểu đóng có toán tử gọi hàm mẫu nội tuyến công khai chấp nhận tham số có tên foo

Do đó, logic sẽ khiến tôi nói ngay từ cái nhìn đầu tiên rằng tham số sẽ che đi biến bị bắt như thể trong:

struct Lambda {
    template<typename T> void operator()(T foo) const { /* ... */ }
    private: decltype(outer_foo) foo{outer_foo};
};

Dù sao, @nm lưu ý chính xác rằng các thành viên dữ liệu không tĩnh được khai báo cho các biến được sao chép thực sự không được đặt tên. Điều đó đang được nói, thành viên dữ liệu chưa được đặt tên vẫn được truy cập bằng phương tiện định danh (nghĩa là foo). Do đó, tên tham số của toán tử gọi hàm vẫn nên (để tôi nói) bóng định danh đó .
Như được chỉ ra chính xác bởi @nm trong các bình luận cho câu hỏi:

thực thể bị bắt ban đầu [...] phải được tạo bóng bình thường theo quy tắc phạm vi

Do đó, tôi muốn nói rằng tiếng kêu là đúng.


Như đã giải thích ở trên, việc tra cứu trong bối cảnh này không bao giờ được thực hiện như thể chúng ta đang ở trong kiểu đóng được chuyển đổi.
Columbo

@Columbo Tôi đang thêm một dòng mà tôi đã bỏ lỡ ngay cả khi nó rõ ràng từ lý do, đó là tiếng kêu đúng. Điều thú vị là tôi đã tìm thấy [expr.prim.lambda] / 8 trong khi cố gắng đưa ra câu trả lời, nhưng tôi đã không thể sử dụng nó đúng như bạn đã làm. Đó là lý do tại sao mỗi khi đọc câu trả lời của bạn là một niềm vui. ;-)
skypjack
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.