C++ auto 타입 추론 에러 | "cannot deduce" 컴파일 에러 해결

C++ auto 타입 추론 에러 | "cannot deduce" 컴파일 에러 해결

이 글의 핵심

C++ auto 타입 추론 에러에 대한 실전 가이드입니다.

들어가며: “auto를 썼더니 타입이 이상해요"

"auto로 받았는데 참조가 아니라 복사가 되었어요”

C++11의 auto타입 추론을 자동화해 코드를 간결하게 만들지만, 참조와 const가 제거되는 등 예상과 다른 타입이 추론될 수 있습니다.

// ❌ 참조 손실
std::vector<int> vec = {1, 2, 3, 4, 5};
auto& ref = vec[0];  // int&
auto val = ref;      // int (참조 제거!)

val = 99;  // vec[0]은 변경 안 됨

이 글에서 다루는 것:

  • auto 타입 추론 규칙
  • 참조와 const 손실
  • auto vs auto& vs auto&&
  • 자주 나오는 auto 에러 8가지
  • AAA (Almost Always Auto) 스타일

목차

  1. auto 타입 추론 규칙
  2. auto vs auto& vs auto&&
  3. 자주 나오는 에러 8가지
  4. AAA 스타일
  5. 정리

1. auto 타입 추론 규칙

규칙 1: 참조 제거

int x = 42;
int& ref = x;

auto a = ref;  // int (참조 제거)
a = 99;        // x는 변경 안 됨

auto& b = ref;  // int& (참조 유지)
b = 99;         // x가 99로 변경

규칙 2: const 제거

const int x = 42;

auto a = x;  // int (const 제거)
a = 99;      // OK

const auto b = x;  // const int (const 유지)
// b = 99;  // 컴파일 에러

규칙 3: 배열 → 포인터

int arr[5] = {1, 2, 3, 4, 5};

auto a = arr;  // int* (배열 → 포인터)
auto& b = arr;  // int (&)[5] (배열 참조)

2. auto vs auto& vs auto&&

auto: 값 복사

std::vector<int> vec = {1, 2, 3};

auto a = vec[0];  // int (복사)
a = 99;           // vec[0]은 변경 안 됨

auto&: 좌측값 참조

std::vector<int> vec = {1, 2, 3};

auto& a = vec[0];  // int& (참조)
a = 99;            // vec[0]이 99로 변경

const auto&: const 참조

std::vector<int> vec = {1, 2, 3};

const auto& a = vec[0];  // const int& (const 참조)
// a = 99;  // 컴파일 에러

auto&&: 전달 참조 (Universal Reference)

int x = 42;

auto&& a = x;       // int& (lvalue → lvalue 참조)
auto&& b = 42;      // int&& (rvalue → rvalue 참조)
auto&& c = vec[0];  // int& (lvalue)

사용 시기: 완벽한 전달 (perfect forwarding).


3. 자주 나오는 에러 8가지

에러 1: 초기화 없음

// ❌ 초기화 필요
auto x;  // 컴파일 에러

// error: declaration of 'auto x' has no initializer

// ✅ 초기화
auto x = 42;

에러 2: 참조 손실

// ❌ 참조 손실
std::vector<int> vec = {1, 2, 3};

for (auto x : vec) {  // int (복사)
    x = 99;  // 복사본 수정
}

// vec은 여전히 {1, 2, 3}

// ✅ 참조 유지
for (auto& x : vec) {  // int&
    x = 99;
}

에러 3: const 손실

// ❌ const 손실
const int x = 42;
auto a = x;  // int (const 제거)
a = 99;      // OK (의도와 다를 수 있음)

// ✅ const 유지
const auto b = x;  // const int
// b = 99;  // 컴파일 에러

에러 4: 프록시 객체 (vector)

// ❌ 프록시 객체
std::vector<bool> vec = {true, false, true};

auto x = vec[0];  // vector<bool>::reference (프록시)
// x는 bool이 아님!

vec.clear();
bool b = x;  // ❌ 댕글링 참조

// ✅ 명시적 타입
bool x = vec[0];  // bool로 변환

에러 5: 초기화 리스트 추론

// ❌ 초기화 리스트
auto x = {1, 2, 3};  // std::initializer_list<int>

// ✅ 명시적 타입
std::vector<int> x = {1, 2, 3};

에러 6: 함수 반환 타입 불일치

// ❌ 여러 return 타입
auto foo(bool flag) {
    if (flag) {
        return 42;      // int
    } else {
        return 3.14;    // double
    }
}

// error: inconsistent deduction for auto return type: 'int' and then 'double'

// ✅ 명시적 타입
double foo(bool flag) {
    if (flag) {
        return 42;
    } else {
        return 3.14;
    }
}

에러 7: 포인터 vs 참조 혼동

// ❌ 포인터로 추론
int x = 42;
auto p = &x;  // int* (포인터)

*p = 99;  // OK
p = nullptr;  // OK (의도와 다를 수 있음)

// ✅ 참조로 명시
auto& r = x;  // int&
r = 99;  // OK
// r = nullptr;  // 컴파일 에러

에러 8: 템플릿 인자 추론 실패

// ❌ 추론 실패
template <typename T>
void foo(T value) {
    auto x = value;  // OK
}

foo(42);  // T = int

// ❌ 함수 포인터
auto func = foo;  // 컴파일 에러 (템플릿 함수)

// error: cannot deduce template arguments

// ✅ 명시적 타입
void (*func)(int) = foo<int>;

4. AAA 스타일

AAA (Almost Always Auto)

AAA거의 모든 곳에 auto를 사용하는 코딩 스타일입니다.

// ❌ 명시적 타입
std::vector<int> vec = {1, 2, 3};
std::vector<int>::iterator it = vec.begin();
std::map<std::string, int>::const_iterator mit = scores.begin();

// ✅ AAA 스타일
auto vec = std::vector<int>{1, 2, 3};
auto it = vec.begin();
auto mit = scores.cbegin();

장점:

  • 코드가 간결
  • 타입 변경 시 수정 최소화
  • 초기화 강제

단점:

  • 타입이 불명확할 수 있음
  • 참조/const 손실 주의

정리

auto 사용 규칙

상황권장이유
읽기만const auto&복사 없음
수정auto&참조
복사 의도auto명확
완벽한 전달auto&&전달 참조
반복자auto간결
함수 반환명시적 타입명확성

auto 에러 방지 체크리스트

  • 초기화를 했는가?
  • 참조가 필요하면 auto&를 사용하는가?
  • const가 필요하면 const auto를 사용하는가?
  • 프록시 객체를 주의하는가? (vector)
  • 함수 반환 타입이 일관적인가?

핵심 규칙

  1. 읽기만: const auto& (복사 없음)
  2. 수정: auto& (참조)
  3. 초기화 필수
  4. 참조와 const 명시 (auto는 제거)
  5. 프록시 객체 주의 (vector)

같이 보면 좋은 글 (내부 링크)

이 주제와 연결되는 다른 글입니다.

  • C++ auto 기초 | 타입 추론 완벽 가이드
  • C++ 타입 추론 | auto·decltype·템플릿
  • C++ decltype | 타입 추론 고급
  • C++ AAA 스타일 | Almost Always Auto

마치며

auto코드를 간결하게 만들지만, 참조와 const가 제거되므로 주의해야 합니다.

핵심 원칙:

  1. 읽기만: const auto&
  2. 수정: auto&
  3. 초기화 필수
  4. 함수 반환은 명시적 타입

const auto&를 습관화하면 불필요한 복사를 제거하고 성능을 높일 수 있습니다.

다음 단계: auto를 이해했다면, C++ 템플릿 타입 추론에서 더 깊이 배워보세요.


관련 글

  • C++ 시리즈 전체 보기
  • C++ Adapter Pattern 완벽 가이드 | 인터페이스 변환과 호환성
  • C++ ADL |
  • C++ Aggregate Initialization |