C++ Lambda Calculus

JunTak Lee·2023년 5월 22일
0

C++ Lambda

목록 보기
1/3
post-thumbnail

유튜브 알고리즘에 낚여서 또 CppCon 영상을 봤다..
https://www.youtube.com/watch?v=1hwRxW99lg0

분명 첫 코드까지만 해도 아무생각이 없었다

auto main() -> int {
    return 0;
}

// Result: 0

근데 바로 뒤에 이게 등장했다

auto main() -> int {
    return -(-![]{});
}

// Result: 0

네?..선생님?..어째서 저게 0인거죠?
그리고 바로 다음 장면에서는 저걸로 1을 만든다

auto main() -> int {
    return (-(-![]{}))-(-!(![]{}));
}

// Result 1

도저히 모르겠어서 요즘 핫하다는 Chat-GPT한테 물어봤다


Chat-GPT의 헛소리

initializer list 어쩌구하면서 헛소리를 정성스럽게 한다
아니 저거 lambda expression이잖아..

valid하지 않은 문법이란다
아니 방금 돌려보고 왔는데 무슨 말도 안되는
그 다음엔 무슨 Undefined Behavior라고 한다
이 새끼 컴파일러 탓도하네..


Eureka!

Chat-GPT에게서 원하는 대답을 얻을 수 없다는걸 깨닫고 구글링을 해보았다
근데 안나온다..
그래서 그냥 무지성으로 Debugging하면서 해석해보았다

auto main() -> int {
    auto lambda = []{};
}

위에 적힌 저 Expression이 Lambda인 것은 자명한 사실이다
그런데 저기에 not operator!을 붙인다면?

auto main() -> int {
    int some_integer = ![]{};
}

갑자기 정수형이 된다
그리고 심지어 저 값은 0이다

auto main() -> int {
    int zero = ![]{};
    return zero;
}

// Result: 0

그때 무심코 한가지 개념이 머리속을 스쳐지나갔다

Lambda는 Capture가 없을 경우, funtion pointer로 변환이 가능합니다

어디에 나와있는지 찾기 귀찮음

Pointer에 not operator!을 취하면 0(NULL)이 된다

auto foo() -> void {}

auto main() -> int {
    void (*ptr)() = foo;
    int zero_from_ptr = !ptr;

    return zero_from_ptr;
}

// Result: 0

이제 모든게 이해가 된다
덕지덕지 붙어있는 Symbol을 걷어내고 나면 결국 나오는건 Pointer다
이제 아래와 같은 단계를 거치면 모든 정수를 얻을 수 있다

  1. Pointer에 !을 취해 0으로 만든다
  2. 다시 !을 취해 1로 만든다
  3. 1을 n번 더해서 숫자 n을 만든다

Validation

이해를 했다면 이제 검증을 해보자
cpp 개발자의 필수 교양 godbolt.org에 아래 코드를 넣고 결과를 뽑아봤다
GCC 13.1 --std=c++2b -O0 -march=skylake

auto main() -> int {
    int zero_from_lambda = ![]{};
}
main:
        push    rbp
        mov     rbp, rsp
        mov     DWORD PTR [rbp-4], 0
        mov     eax, 0
        pop     rbp
        ret

Optimization이 없는데도 바로 0으로 만들어버린다
이제 function pointer에서도 동일한 결과가 나오는지 확인해보자
GCC 13.1 --std=c++2b -O0 -march=skylake

auto foo() -> void {} 

auto main() -> int {
    int zero_from_func_ptr = !(&foo);
}
foo():
        push    rbp
        mov     rbp, rsp
        nop
        pop     rbp
        ret
main:
        push    rbp
        mov     rbp, rsp
        mov     DWORD PTR [rbp-4], 0
        mov     eax, 0
        pop     rbp
        ret

완벽하게 동일한 결과가 나온다
우리가 적은 code는 mov DWORD PTR [rbp-4], 0부분이다
즉, lambda가 function pointer와 같이 바뀌었음을 확인할 수 있다


단순한 개념인데 이걸 놓쳐서 1시간 삽질을 했다
화나서 Chat-GPT한테 욕하다가 답변을 다시보니 정답과 비슷한 답변이 있었다
무려 5번의 질문 끝에 정답에 가까운 소리를 하기 시작했다..

behavior에서 아래 2개는 그냥 개소리고 2번째도 엄밀히 말하면 틀린말이다
!를 붙여서 function pointer처럼 동작하는게 아니다..
그리고 그 뒤에 왜 이상한걸 갖다 붙이니..
이 새낀 Initializer List를 좋아하는게 분명하다

0개의 댓글