+를 사용하여 람다에 대한 함수 포인터 및 std :: function에 대한 모호한 오버로드 해결


93

다음 코드에서는에 대한 첫 번째 호출 foo이 모호하므로 컴파일에 실패합니다.

두 번째 +는 람다 앞에 추가 된 함수 포인터 오버로드로 해결됩니다.

#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)
}

+여기서 표기법 은 무엇입니까 ?

답변:


99

+발현은 +[](){}단항이며 +연산자. [expr.unary.op] / 7에서 다음과 같이 정의됩니다.

단항 +연산자 의 피연산자 는 산술, 범위가 지정되지 않은 열거 또는 포인터 유형을 가져야하며 결과는 인수의 값입니다.

람다는 산술 유형 등이 아니지만 변환 할 수 있습니다.

[expr.prim.lambda] / 3

의 타입 람다 식 [...] 고유 이름 불유합 클래스 타입 - 착신 폐쇄 형 - 그 등록을 설명한다.

[expr.prim.lambda] / 6

A에 대한 폐쇄 형 람다 식 아니오 람다 캡처 갖는 publicvirtualexplicit const로 변환 기능 함수 포인터 폐쇄 형의 함수 호출 연산자와 같은 파라미터 및 반환을 가지도록한다. 이 변환 함수에 의해 반환 된 값은 호출 될 때 클로저 유형의 함수 호출 연산자를 호출하는 것과 동일한 효과를 갖는 함수의 주소 여야합니다.

따라서 단항 +은이 lambda에 대한 함수 포인터 유형으로 변환을 강제합니다 void (*)(). 따라서 표현식의 유형 +[](){}은이 함수 포인터 유형 void (*)()입니다.

두 번째 오버로드 void foo(void (*f)())는 오버로드 해결 순위에서 정확히 일치하므로 명확하게 선택됩니다 (첫 번째 오버로드는 정확히 일치하지 않음).


람다 [](){}는의 std::function<void()>비명 시적 템플릿 ctor 를 통해 로 변환 될 수 있으며 std::function, 이는 CallableCopyConstructible요구 사항 을 충족하는 모든 유형을 취합니다 .

람다는 클로저 유형void (*)() 의 변환 함수 를 통해 변환 할 수도 있습니다 (위 참조).

둘 다 사용자 정의 변환 시퀀스이며 순위가 동일합니다. 이것이 첫 번째 예에서 모호성으로 인해 과부하 해결이 실패하는 이유 입니다.


Daniel Krügler의 주장으로 뒷받침 된 Cassio Neri에 따르면,이 단항 +트릭은 동작을 지정해야합니다. 즉, 신뢰할 수 있습니다 (코멘트의 토론 참조).

그래도 모호함을 피하려면 함수 포인터 유형에 대한 명시 적 캐스트를 사용하는 것이 좋습니다. 그래서 무엇을하고 왜 작동하는지 물어볼 필요가 없습니다.)


3
@Fred AFAIK 멤버 함수 포인터는 함수 lvalue는 고사하고 비 멤버 함수 포인터로 변환 될 수 없습니다. 함수 lvalue와 유사하게 호출 할 수 std::bind있는 std::function객체를 통해 멤버 함수를 바인딩 할 수 있습니다.
dyp '2013-07-23

2
@DyP : 나는 우리가 까다로운 것에 의지 할 수 있다고 믿습니다. 실제로 구현 operator +()이 상태 비 저장 클로저 유형에 추가한다고 가정합니다 . 이 연산자는 클로저 유형이 변환하는 함수에 대한 포인터가 아닌 다른 것을 반환한다고 가정합니다. 그러면 5.1.2 / 3을 위반하는 프로그램의 관찰 가능한 동작이 변경됩니다. 이 이유에 동의하면 알려주세요.
Cassio Neri

2
@CassioNeri 예, 그것이 제가 확실하지 않은 지점입니다. 를 추가 할 때 관찰 가능한 동작이 바뀔 수 있다는 데 동의 operator +하지만 이것은 operator +시작할 수 없는 상황과 비교하는 것입니다 . 그러나 클로저 유형에 operator +과부하 가 없어야한다는 것은 지정되지 않았습니다 . "구현은 아래에 설명 된 것과 다르게 클로저 유형을 정의 할 수 있습니다. 단, 이것이 [...]에 의한 것 이외의 프로그램의 관찰 가능한 동작을 변경하지 않는 경우"그러나 IMO 에서 연산자를 추가 해도 클로저 유형이 무엇과 다른 것으로 변경되지는 않습니다. "아래 설명"입니다.
dyp '2013-07-24

3
@DyP :없는 상황 operator +()은 표준에서 설명하는 것과 정확히 일치합니다. 표준은 구현이 지정된 것과 다른 작업을 수행 할 수 있도록합니다. 예를 들어 operator +(). 그러나 프로그램에서이 차이를 관찰 할 수 있다면 이는 불법입니다. 일단 내가 comp.lang.c ++. moderated에서 클로저 유형이 typedef를 추가 할 수 있는지, result_type그리고 다른 유형은 typedefs적응 가능하게 만드는 데 필요한지 물었습니다 (예 :) std::not1. 관찰 할 수 있기 때문에 할 수 없다고 들었습니다. 링크를 찾아 보겠습니다.
카시오 네리

6
VS15는 다음과 같은 재미있는 오류를 제공합니다. test.cpp (543) : 오류 C2593 : '연산자 +'는 모호합니다 t \ test.cpp (543) : 참고 : '내장 C ++ operator + (void (__cdecl *) (void )) 't \ test.cpp (543) : 참고 : 또는'내장 C ++ 연산자 + (void (__stdcall *) (void)) 't \ test.cpp (543) : 참고 : 또는'내장 C ++ 연산자 + (void (__fastcall *) (void)) 't \ test.cpp (543) : 참고 : 또는'내장 C ++ 연산자 + (void (__vectorcall *) (void)) 't \ test.cpp (543) : 참고 : 인수 목록 '(wmain :: <lambda_d983319760d11be517b3d48b95b3fe58>) test.cpp (543) : 오류 C2088 :'+ ': 잘못된 클래스
Ed Lambert
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.