← 문제로

17. auto / decltype / decltype(auto) 추론 규칙과 decltype((expr)) 괄호 함정

난이도 상
내 리뷰 · C++
해설 · C++

해설 — auto / decltype / decltype(auto) 추론 규칙과 decltype((expr)) 괄호 함정

난이도: 상

요약

auto 는 템플릿 인자 추론과 같은 규칙으로 참조·cv 를 떨어뜨리고 값으로 추론한다. decltype(e) 는 e 의 선언 타입을 보존하되 괄호로 싸인 lvalue 표현식이면 참조가 된다. decltype(auto) 는 초기화식에 decltype 규칙을 그대로 적용해 참조/cv 를 보존한다.

예측 출력

w.v=77 a=99 b=10
is_ref a: 0
is_ref b: 0
is_ref c: 1
global=1000

핵심 개념 · 단계별

  • auto a = r;rint& 지만 auto 는 참조를 벗겨 int. aw.v복사본. a=99w.v 에 무영향.
  • decltype(w.v) b = w.v;w.v 는 멤버 접근(이름) → 선언 타입 int. b 는 복사본(10).
  • decltype((w.v)) c = w.v;괄호 ((w.v)) 는 표현식이고 w.v 는 lvalue → decltype 규칙상 int&. cw.v 의 별칭. c=77w.v 를 77로 바꾼다.
  • 그래서 w.v=77(c로 변경), a=99(별개 복사본), b=10(변경 전 복사).
  • is_reference: a→0(int), b→0(int), c→1(int&).
  • decltype(auto) d = getRef(); — 초기화식 getRef() 의 타입은 int&decltype(auto)int& 보존. dglobal 의 별칭. d=1000global=1000.
    • 만약 auto d = getRef(); 였다면 참조가 벗겨져 int 복사본이 되어 d=1000global 에 영향 없고 출력은 global=42 였을 것.

흔한 오해·함정

  • decltype(x) vs decltype((x)): 이름 그대로면 선언 타입, 괄호 한 겹이면 lvalue→참조. 이 차이가 decltype(auto) 반환 타입에서 버그를 만든다.
  • 제네릭 getter 의 댕글링/복사: decltype(auto) get() { return obj.member; }member 의 선언 타입을 보존(보통 의도대로 참조 반환)하지만, return (obj.member); 처럼 괄호를 붙이면 lvalue 참조로 추론돼, 지역 변수를 그렇게 반환하면 댕글링 참조가 된다. 반대로 auto get() 은 항상 복사라 큰 객체에서 불필요한 복사·성능 손실.
  • forwarding 과의 관계: T&&(보편 참조, cpp_internals/problem8)는 인자 추론 규칙으로 lvalue/rvalue 를 구분하고, decltype(auto) 는 그 값 카테고리를 반환 타입에 그대로 전달(완벽한 반환)할 때 짝으로 쓰인다 — decltype(auto) f(T&& x){ return std::forward<T>(x); }. 둘은 다른 메커니즘이지만 "값 카테고리 보존"이라는 목적에서 만난다.

면접 포인트

  • auto(값, 참조·cv 탈락) vs decltype(선언 타입, 괄호 시 참조) vs decltype(auto)(초기화식 decltype 보존)의 3분 비교.
  • decltype((x)) 괄호 규칙이 제네릭 코드에서 복사 vs 참조, 나아가 댕글링을 가르는 실제 사례.
  • 반환 타입을 auto 로 둘 때의 의도치 않은 복사와 decltype(auto)/const auto& 로 교정하는 법.