icon안동민 개발노트

auto 키워드와 타입 추론


auto 키워드의 소개

 C++ 11에서 도입된 auto 키워드는 변수의 타입을 컴파일러가 자동으로 추론하도록 해줍니다.

 이는 코드의 가독성을 높이고, 리팩토링을 용이하게 만들며, 템플릿 프로그래밍을 더욱 쉽게 만들어줍니다.

 기본 사용법

auto i = 42;        // int로 추론
auto f = 3.14;      // double로 추론
auto s = "Hello";   // const char*로 추론
auto v = {1, 2, 3}; // std::initializer_list<int>로 추론

auto의 타입 추론 규칙

 auto의 타입 추론은 템플릿 타입 추론과 유사하지만, 완전히 동일하지는 않습니다.

 주요 규칙은 다음과 같습니다.

  1. 초기화 표현식이 참조인 경우, 참조성은 무시됩니다.
  2. constvolatile 한정자는 보통 무시됩니다. (참조나 포인터가 아닌 경우)
  3. 중괄호 초기화 {}std::initializer_list로 추론됩니다.
예제
int x = 27;
const int cx = x;
const int& rx = x;
 
auto a = x;    // int
auto b = cx;   // int (const 무시)
auto c = rx;   // int (const와 참조성 무시)
auto& d = x;   // int&
auto& e = cx;  // const int&
auto& f = rx;  // const int&

auto와 함께 사용되는 수식어

 auto는 다양한 수식어와 함께 사용될 수 있습니다.

const auto ca = x;      // const int
auto& ra = x;           // int&
auto* pa = &x;          // int*
const auto& cra = x;    // const int&

함수 반환 타입으로서의 auto

 C++14부터는 함수의 반환 타입으로 auto를 사용할 수 있습니다.

auto add(int a, int b) {
    return a + b;  // 반환 타입은 int로 추론
}
 
auto get_vector() {
    return std::vector<int>{1, 2, 3};  // std::vector<int> 반환
}

 이는 특히 복잡한 반환 타입을 가진 함수나 템플릿 함수에서 유용합니다.

decltype과 auto

 decltype은 표현식의 타입을 추론합니다.

 autodecltype을 함께 사용하면 더욱 강력한 타입 추론이 가능합니다.

template<typename T, typename U>
auto add(T t, U u) -> decltype(t + u) {
    return t + u;
}

 이를 "후행 반환 타입 구문"이라고 합니다.

 C++14부터는 대부분의 경우 다음과 같이 간단히 쓸 수 있습니다.

template<typename T, typename U>
auto add(T t, U u) {
    return t + u;
}

auto와 람다 표현식

 auto는 다음 절에서 다룰 람다 표현식의 타입을 캡처하는 데 매우 유용합니다.

auto lambda = [](int x, int y) { return x + y; };

 C++ 14부터는 람다 매개변수에도 auto를 사용할 수 있어 제네릭 람다를 만들 수 있습니다.

auto generic_lambda = [](auto x, auto y) { return x + y; };

auto의 장단점

 장점

  1. 코드의 간결성과 가독성 향상
  2. 타입 불일치 오류 감소
  3. 리팩토링 용이성
  4. 긴 타입 이름 작성 불필요

 단점

  1. 코드만 봐서는 정확한 타입을 알기 어려울 수 있음
  2. 의도치 않은 타입 추론 가능성
  3. 초보자에게는 이해하기 어려울 수 있음

auto 사용 시 주의사항

  1. 초기화 리스트와 함께 사용 시 주의
auto x1 = {1, 2, 3};  // std::initializer_list<int>
auto x2{1};           // std::initializer_list<int>, C++17 이전
auto x3 = {1};        // std::initializer_list<int>
  1. 프록시 클래스와의 상호작용
std::vector<bool> v = {true, false, true};
auto x = v[0];  // std::vector<bool>::reference, 실제 bool이 아님
  1. 가독성 저하 방지

 때로는 명시적 타입이 코드의 의도를 더 잘 전달할 수 있습니다.

 auto의 과도한 사용은 오히려 가독성을 해칠 수 있습니다.

auto와 코딩 스타일

 auto의 사용에 대한 일반적인 가이드라인

  1. 명확한 경우에만 auto 사용
  2. 복잡한 타입이나 템플릿 타입에 주로 사용
  3. 반복자나 람다 표현식에는 거의 항상 auto 사용
  4. 코드의 의도를 불분명하게 만들지 않도록 주의

실습 : auto를 활용한 코드 개선

 다음은 auto를 사용하여 코드를 개선하는 예제입니다.

// 개선 전
std::map<std::string, std::vector<int>> data;
for (std::map<std::string, std::vector<int>>::iterator it = data.begin(); 
     it != data.end(); ++it) {
    for (std::vector<int>::const_iterator vit = it->second.begin(); 
         vit != it->second.end(); ++vit) {
        // 처리 로직
    }
}
 
// 개선 후
std::map<std::string, std::vector<int>> data;
for (const auto& [key, value] : data) {  // C++17 구조화된 바인딩 사용
    for (const auto& num : value) {
        // 처리 로직
    }
}

연습 문제

  1. 다음 코드에서 auto로 선언된 변수들의 실제 타입을 추론해보세요.
int x = 10;
const int* p = &x;
int& r = x;
 
auto a = x;
auto b = p;
auto c = *p;
auto& d = r;
auto e = {1, 2, 3};
  1. 다음 함수 템플릿을 autodecltype을 사용하여 개선해보세요.
template<typename T, typename U>
? multiply(T t, U u) {
    return t * u;
}

 참고자료