같은 이름의 Lambda 캡처 및 매개 변수-누가 다른 사람을 섀도 잉합니까? (clang 대 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 이상 은 "You 're using clang ++!"을 출력합니다. 캡처 foo 가 사용되지 않았 음을 경고합니다 .

  • g ++ 4.9.0 이상 은 "You 're using g ++!"을 출력합니다. 사용되지 않는 매개 변수 foo 에 대해 경고합니다 .

여기서 C ++ 표준을 더 정확하게 따르는 컴파일러는 무엇입니까?

지팡이 상자 예


1
wandbox에서 여기로 코드를 붙여 넣으면 (공유 버튼을 잊은 것 같음) VS2015 (?)가 C4458 : 'foo'선언이 클래스 멤버를 숨 깁니다 .
— nwp

12
좋은 예 ..
— deviantfan

4
람다에는 템플릿 함수 호출 연산자가있는 유형이 있으므로 논리에 따라 매개 변수가 struct Lambda { template<typename T> void operator()(T foo) const { /* ... */ } private: decltype(outer_foo) foo{outer_foo}; }.
— skypjack

2
@nwp VS가 잘못되었으며 람다의 데이터 멤버는 이름이 지정되지 않았으므로 섀도 잉 할 수 없습니다. 표준에 따르면 "캡처 된 엔터티에 대한 액세스는 해당 데이터 멤버에 대한 액세스로 변환됩니다"라고되어 있으며, 이는 우리를 정사각형으로 남겨 둡니다.
— n. '대명사'm.

10
clang 버전이 정확하기를 바랍니다. 함수 외부의 무언가가 함수 매개 변수에 그림자를 드리 우면 그 반대가 아닌 새로운 영역을 개척 할 것입니다!
— MM

답변:


65

업데이트 : 하단 인용문에서 Core 의자가 약속 한대로 코드는 이제 잘못된 형식입니다 .

단순 캡처 의 식별자 가 lambda-declarator 의 parameter-declaration-clause 매개 변수의 declarator-id 로 나타나면 프로그램의 형식이 잘못된 것입니다.


얼마 전에 람다에서 이름 조회와 관련된 몇 가지 문제가있었습니다. N2927에 의해 해결되었습니다 .

새로운 문구는 더 이상 캡처 된 엔티티의 사용을 다시 매핑하기 위해 조회에 의존하지 않습니다. 람다의 복합 문 이 두 번의 패스로 처리되거나 해당 복합 문의 이름 이 클로저 유형의 멤버로 해석 될 수 있다는 해석을 더 명확하게 거부합니다 .

조회는 항상 lambda-expression 의 컨텍스트에서 수행되며 클로저 유형의 멤버 함수 본문으로의 변환 "이후"가 아닙니다. [expr.prim.lambda] / 8 참조 :

람다 식 의 화합물 문 수율 함수 바디 함수 호출 연산자 ([dcl.fct.def])로하지만, 이름 조회를 위하여, [...] 상기 화합물 문 의 맥락에서 고려 람다 표현 . [ 예 :

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*
    }; 
  }
};

— 최종 예 ]

(이 예는 또한 조회가 클로저 유형의 생성 된 캡처 멤버를 어떻게 든 고려하지 않음을 분명히합니다.)

이름 foo은 캡처에서 (재) 선언되지 않습니다. 람다 식을 둘러싸는 블록에서 선언됩니다. 매개 변수 foo는 해당 외부 블록에 중첩 된 블록에서 선언됩니다 ( 람다 매개 변수도 명시 적으로 언급하는 [basic.scope.block] / 2 ). 조회 순서는 내부 블록에서 외부 블록으로 명확 합니다. 입니다. 따라서 매개 변수를 선택해야합니다. 즉, Clang이 맞습니다.

캡처를 init-capture, 즉 foo = ""대신 으로 만들면 foo대답이 명확하지 않습니다. 이것은 지금 캡처가 실제로 "블록"이 제공되지 않은 선언을 유도 입니다. 나는 이것에 대해 핵심 의장에게 메시지를 보냈다.

이 문제는 2211 호입니다 (불행하게도 여러 문제에 대한 자리 표시 자만있는 상태로 곧 open-std.org 사이트에 새로운 문제 목록이 표시됩니다.이 중 하나입니다. 저는 Kona 이전에 이러한 공백을 채우기 위해 열심히 노력하고 있습니다. 월말 회의). CWG는 1 월 원격 회의에서이 문제를 논의했으며 , 캡처 이름이 매개 변수 이름 인 경우 프로그램을 잘못 구성하는 것이 방향입니다.


여기서 뜯어 낼 것은 없습니다 :) 단순 캡처 는 아무것도 선언하지 않으므로 이름 조회의 올바른 결과는 상당히 분명합니다 (BTW, GCC는 명시 적 캡처 대신 캡처 기본값 을 사용하는 경우 올바르게 가져옵니다 ). init-capture 는 다소 까다 롭습니다.
— TC

1
@TC 동의합니다. 핵심 문제를 제기했지만 이미 논의 된 것 같습니다. 편집 된 답변을 참조하세요.
— Columbo 2017

6

나는 당신에게 의미있는 대답을 제공하기 위해 질문에 대한 몇 가지 의견을 모으고 있습니다.
먼저 다음 사항에 유의하십시오.

  • 복사 캡처 된 각 변수의 람다에 대해 비 정적 데이터 멤버가 선언됩니다.
  • 특정 경우에 람다는 다음과 같은 매개 변수를 허용하는 공용 인라인 템플릿 함수 호출 연산자가있는 클로저 유형을 갖습니다. foo

따라서 논리는 매개 변수가 다음과 같이 캡처 된 변수를 섀도 잉해야한다고 언뜻보기에 말하게합니다.

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

어쨌든 @nm은 복사 캡처 변수에 대해 선언 된 비 정적 데이터 멤버가 실제로 이름이 지정되지 않았다고 올바르게 지적했습니다. 즉, 이름이 지정되지 않은 데이터 멤버는 여전히 식별자 (즉 foo)를 통해 액세스됩니다 . 따라서 함수 호출 연산자의 매개 변수 이름은 여전히 해당 식별자를 숨겨야합니다 .
질문에 대한 주석에서 @nm가 올바르게 지적한대로 :

원래 캡처 된 엔티티 [...]는 범위 규칙에 따라 정상적으로 음영 처리되어야합니다.

그 때문에 clang이 옳다고 말하고 싶습니다.


위에서 설명한 것처럼이 컨텍스트에서 조회는 변환 된 클로저 유형에있는 것처럼 수행되지 않습니다.
— Columbo

@Columbo 추론에서 분명 했음에도 불구하고 내가 놓친 줄을 추가하고 있는데, 그것은 clang이 옳다는 것입니다. 재미있는 부분은 대답을하려고하다가 [expr.prim.lambda] / 8을 찾았지만 당신처럼 제대로 사용하지 못했다는 것입니다. 그렇기 때문에 매번 답변을 읽는 것이 즐겁습니다. ;-)
— skypjack
당사 사이트를 사용함과 동시에 당사의 쿠키 정책과 개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.