[C/C++] 타입 추론(Type Deduction), 범위 기반 반복문(Range-based for loop)

할랑말랑·2026년 3월 12일

C/C++

목록 보기
21/45

타입 추론(Type Deduction)

컴파일러가 변수의 타입을 자동으로 결정하는 기능
개발자가 명시적으로 타입을 작성하지 않아도 컴파일러가 초기화 값을 보고 타입을 추론한다.

1. auto (C++11)

기본 개념

변수 선언 시 타입을 auto로 지정하면 초기화 값으로부터 타입을 추론합니다.

장점

  • 코드가 간결해짐
  • 타입 변경 시 유지보수 용이
  • 복잡한 타입명 작성 불필요

단점

  • 타입이 명확하지 않을 수 있음
  • 초기화가 반드시 필요
#include <iostream>
#include <vector>

int main()
{
    // =================================================================
    // C++11: 타입 추론 (auto)
    // =================================================================
    std::cout << "--- 기본 타입 추론 ---" << std::endl;
    // 1. 기본 타입
    auto x = 10;       // 초기화 값 10을 보고 int로 추론
    auto y = 3.14;     // 3.14를 보고 double로 추론
    auto s = "Hello";  // 문자열 리터럴을 보고 const char* 로 추론

    std::cout << "x = 10;   -> x는 int 타입" << std::endl;
    std::cout << "y = 3.14; -> y는 double 타입" << std::endl;
    std::cout << "s = \"Hello\"; -> s는 const char* 타입" << std::endl;

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

    // 2. 반복자 (복잡한 타입 이름 대체)
    // auto를 사용하지 않을 경우: std::vector<int>::iterator it1 = v.begin();
    // auto를 사용하면 코드가 훨씬 간결해집니다.
    auto it1 = v.begin();
    std::cout << "\n반복자의 첫 번째 요소: " << *it1 << std::endl;

    // =================================================================
    // 3. 범위 기반 for문 (Range-based for loop)
    // =================================================================
    std::cout << "\n--- 범위 기반 for문 ---" << std::endl;
    std::cout << "원본 벡터 v: ";
    for (int elem : v) { std::cout << elem << " "; }
    std::cout << std::endl;

    // 'for (auto elem : v)' -> 요소를 값으로 복사 (call-by-value)
    std::cout << "\n'for (auto elem : v)' 실행 (값 복사):" << std::endl;
    std::cout << "  -> 루프 안에서 값을 수정해도 원본 벡터는 바뀌지 않음" << std::endl;
    for (auto elem : v)
    {
        // 여기서 elem은 v의 원소를 복사한 새로운 변수입니다.
        std::cout << elem << " ";
    }
    std::cout << std::endl;

    // 'for (auto& elem : v)' -> 요소를 참조로 가져옴 (call-by-reference)
    std::cout << "\n'for (auto& elem : v)' 실행 (참조):" << std::endl;
    std::cout << "  -> 루프 안에서 값을 수정하면 원본 벡터가 바뀜" << std::endl;
    for (auto& elem : v)
    {
        // 여기서 elem은 v의 원소 자체를 가리키는 별명(참조)입니다.
        elem *= 2;
    }

    std::cout << "수정 후 벡터 v: ";
    for (auto elem : v)
    {
        std::cout << elem << " ";
    }
    std::cout << std::endl;

    // =================================================================
    // 4. 람다(Lambda) 함수
    // =================================================================
    std::cout << "\n--- 람다 함수 ---" << std::endl;
    // 람다 함수의 실제 타입은 복잡하고 이름이 없으므로 auto로 받는 것이 편리합니다.
    auto lambda = [](int val) {
        return val * 2;
    };

    int result = lambda(5); // lambda 함수 호출
    std::cout << "lambda(5) 실행 결과: " << result << std::endl;

    return 0;
}

2. auto (C++14)

#include <iostream>
#include <vector>

// =================================================================
// 1. 함수의 반환 타입 추론 (Return Type Deduction)
// =================================================================

// C++14부터 함수의 반환 타입을 auto로 지정하면, 컴파일러가 return 문을 보고
// 반환 타입을 자동으로 추론합니다.
auto add(int a, int b)
{
    // a + b의 결과가 int이므로, 이 함수의 반환 타입은 int로 추론됩니다.
    return a + b;
}

// std::vector<int> 와 같이 복잡한 타입도 간단하게 표현할 수 있습니다.
auto getVector()
{
    return std::vector<int>{ 1, 2, 3, 4 };
}

// 함수 내에 return 문이 여러 개 있더라도, 모두 같은 타입으로 추론된다면 사용 가능합니다.
auto getValue(bool flag)
{
    if (flag) {
        return 20; // int로 추론
    } else {
        return 30; // int로 추론
    }
    // 만약 return 30.0; 처럼 다른 타입이 섞여있으면 컴파일 에러가 발생합니다.
}

int main()
{
    std::cout << "--- 1. 함수 반환 타입 추론 ---" << std::endl;
    std::cout << "add(10, 20) 결과: " << add(10, 20) << std::endl;

    auto gv = getVector(); // gv는 std::vector<int> 타입으로 추론됨
    std::cout << "getVector() 결과: ";
    for (auto i : gv)
    {
        std::cout << i << " ";
    }
    std::cout << std::endl;

    // =================================================================
    // 2. 제네릭 람다 (Generic Lambda)
    // =================================================================
    std::cout << "\n--- 2. 제네릭 람다 ---" << std::endl;
    // C++14부터 람다 함수의 매개변수에도 auto를 사용할 수 있습니다.
    // 이는 이 람다 함수가 템플릿처럼 동작하게 만들어 줍니다.
    auto generic_lambda = [](auto a, auto b) {
        return a + b;
    };

    // 다양한 타입으로 람다 함수를 호출할 수 있습니다.
    std::cout << "generic_lambda(10, 10) 결과 (int): " << generic_lambda(10, 10) << std::endl;
    std::cout << "generic_lambda(3.14, 2.71) 결과 (double): " << generic_lambda(3.14, 2.71) << std::endl;

    // =================================================================
    // 3. decltype(auto) : 정확한 타입 추론
    // =================================================================
    std::cout << "\n--- 3. decltype(auto) ---" << std::endl;
    int x = 10;
    int& ref = x; // ref는 x에 대한 참조(별명)

    // 가. 'auto'의 타입 추론 방식 (참조 속성 제거)
    std::cout << "가. 'auto'는 참조(reference) 속성을 제거합니다." << std::endl;
    auto a = ref; // ref는 int& 타입이지만, auto는 참조 속성을 제거하고 int 타입으로 추론.
                  // 따라서 'a'는 x의 값을 복사한 새로운 int 변수가 됩니다.
    a = 20;       // 'a'의 값을 바꿔도 원본 'x'에는 영향이 없습니다.
    std::cout << "  auto a = ref; a = 20; 실행 후 -> x의 값: " << x << " (변화 없음)" << std::endl;

    // 나. 'decltype(auto)'의 타입 추론 방식 (참조 속성 유지)
    std::cout << "나. 'decltype(auto)'는 참조(reference) 속성을 그대로 유지합니다." << std::endl;
    decltype(auto) b = ref; // decltype(ref)는 int& 이므로, 'b'는 int& 타입으로 추론됨.
                            // 따라서 'b'는 원본 'x'를 가리키는 또 다른 참조가 됩니다.
    b = 30;                 // 'b'의 값을 바꾸면 원본 'x'가 직접 수정됩니다.
    std::cout << "  decltype(auto) b = ref; b = 30; 실행 후 -> x의 값: " << x << " (수정됨)" << std::endl;

    return 0;
}
  • 함수 반환 타입 추론
  • 제네릭 람다 - [](auto param){ ... }: 람다 함수의 매개변수 타입에 auto를 사용할 수 있게 되었다.
  • decltype(auto) - decltype의 규칙을 따라 타입을 추론합니다. 즉, 표현식의 타입과 속성(참조, const 등)을 정확하게 그대로 유지한다.

3. 범위 기반 반복문 (Range-based for loop)

C++11부터 도입된 문법으로, 배열이나 컨테이너(vector, list 등)의 요소를 하나씩 꺼내어 반복할 때 사용

특징

  • 처음부터 끝까지 자동으로 순회
  • 다양한 컨테이너 지원
  • 가독성 : 간결하고 명확하다.
  • 제한적 : 인덱스, 역순, 일부 범위 불가
  • 안전성 : 경계 오류 없음
#include <iostream>
#include <vector>
#include <string>

// 벡터의 현재 상태를 출력해주는 도우미 함수
void printVector(const std::string& message, const std::vector<int>& vec)
{
    std::cout << message << "[ ";
    for (int elem : vec)
    {
        std::cout << elem << " ";
    }
    std::cout << "]" << std::endl;
}

int main()
{
    std::vector<int> v = { 1, 2, 3, 4, 5 };
    printVector("초기 벡터 상태: ", v);
    std::cout << "\n==================================================" << std::endl;

    // --- 1. 전통적인 C-스타일 for문 (인덱스 기반) ---
    std::cout << "1. 전통적인 C-스타일 for문 (인덱스 기반)" << std::endl;
    for (int i = 0; i < v.size(); ++i)
    {
        std::cout << "v[" << i << "] = " << v[i] << std::endl;
    }
    std::cout << "==================================================" << std::endl;

    // --- 2. 반복자(Iterator)를 사용하는 for문 ---
    std::cout << "2. 반복자를 사용하는 for문" << std::endl;
    for (std::vector<int>::iterator it = v.begin(); it != v.end(); ++it)
    {
        std::cout << "iterator가 가리키는 값: " << *it << std::endl;
    }
    std::cout << "==================================================" << std::endl;

    // --- 3. 범위 기반 for문 (C++11 이상) ---
    std::cout << "3. 범위 기반 for문 (C++11 이상)" << std::endl;

    // 가. 값 복사 (Call by Value)
    std::cout << "\n  가. 값에 의한 복사 (for (int i : v))" << std::endl;
    for (int i : v)
    {
        i += 1; // 'i'는 벡터 원소의 복사본이므로, 이 연산은 원본 벡터 'v'에 영향을 주지 않음
    }
    printVector("  -> 값 복사 루프 실행 후 벡터 (변화 없음): ", v);

    // 나. 참조 (Call by Reference)
    std::cout << "\n  나. 참조에 의한 접근 (for (int& i : v))" << std::endl;
    for (int& i : v)
    {
        i += 1; // 'i'는 벡터 원소 자체를 가리키는 참조이므로, 원본 벡터 'v'의 값이 직접 수정됨
    }
    printVector("  -> 참조 루프 실행 후 벡터 (원본 수정됨): ", v);

    // 다. 읽기 전용 참조 (const reference) - 가장 권장되는 읽기 방식
    std::cout << "\n  다. 읽기 전용 (for (const auto& i : v))" << std::endl;
    std::cout << "  (수정된 벡터의 내용을 효율적이고 안전하게 출력)" << std::endl;
    for (const auto& i : v)
    {
        // i += 1; // const 참조이므로 컴파일 에러 발생! 값을 수정할 수 없음.
        std::cout << "  읽어온 값: " << i << std::endl;
    }
    std::cout << "==================================================" << std::endl;

    return 0;
}
  • 전통적인 for문 : for (int i = 0; ...) 형태로, 인덱스 변수 i를 직접 제어
  • 반복자(Iterator)를 사용하는 for문 : 컨테이너의 시작(begin())과 끝(end())을 가리키는 포인터와 유사한 '반복자'를 사용
  • 범위 기반 for문 : for (변수 선언 : 컨테이너) 형태로, 컨테이너의 모든 원소를 처음부터 끝까지 순회하는 가장 간결하고 현대적인 방법

0개의 댓글