C++ 범위 기반 for문 에러 | "no begin function" 컴파일 에러 해결

C++ 범위 기반 for문 에러 | "no begin function" 컴파일 에러 해결

이 글의 핵심

C++ 범위 기반 for문 에러에 대한 실전 가이드입니다.

들어가며: “범위 기반 for문에서 에러가 나요"

"for (auto x : vec)가 안 돼요”

C++11의 범위 기반 for문(Range-based for loop)은 컨테이너 순회를 간결하게 만들지만, 잘못 사용하면 컴파일 에러크래시가 발생합니다.

// ❌ 에러 코드
for (auto x : myCustomType) {  // begin()/end() 없음
    std::cout << x << '\n';
}

// error: no matching function for call to 'begin(MyCustomType&)'

이 글에서 다루는 것:

  • 범위 기반 for문 에러 8가지
  • begin()/end() 요구사항
  • 값 vs 참조 캡처
  • 반복자 무효화 주의
  • 임시 객체 수명

목차

  1. 범위 기반 for문 동작 원리
  2. 자주 나오는 에러 8가지
  3. 커스텀 타입 지원
  4. 성능 최적화
  5. 정리

1. 범위 기반 for문 동작 원리

내부 변환

// 범위 기반 for
for (auto x : vec) {
    std::cout << x << '\n';
}

// 컴파일러가 변환 (개념적)
{
    auto&& __range = vec;
    auto __begin = std::begin(__range);
    auto __end = std::end(__range);
    
    for (; __begin != __end; ++__begin) {
        auto x = *__begin;
        std::cout << x << '\n';
    }
}

요구사항: begin()end() 함수 필요.


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

에러 1: no begin function

// ❌ begin()/end() 없음
struct MyRange {
    int data[5] = {1, 2, 3, 4, 5};
};

MyRange range;
for (auto x : range) {  // begin()/end() 없음
    std::cout << x << '\n';
}

// error: no matching function for call to 'begin(MyRange&)'

해결: begin()/end() 정의.

// ✅ 멤버 함수
struct MyRange {
    int data[5] = {1, 2, 3, 4, 5};
    
    int* begin() { return data; }
    int* end() { return data + 5; }
};

// ✅ 또는 비멤버 함수
int* begin(MyRange& r) { return r.data; }
int* end(MyRange& r) { return r.data + 5; }

에러 2: 값 복사 (수정 안 됨)

// ❌ 복사 (원본 수정 안 됨)
std::vector<int> vec = {1, 2, 3, 4, 5};

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

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

// ✅ 참조로 수정
for (auto& x : vec) {  // 참조
    x = 99;
}

// vec은 이제 {99, 99, 99, 99, 99}

에러 3: const 불일치

// ❌ const 객체에서 비const 참조
void print(const std::vector<int>& vec) {
    for (auto& x : vec) {  // auto& → int&
        std::cout << x << '\n';
    }
}

// error: binding reference of type 'int&' to 'const int' discards qualifiers

// ✅ const 참조
void print(const std::vector<int>& vec) {
    for (const auto& x : vec) {  // const auto&
        std::cout << x << '\n';
    }
}

에러 4: 반복 중 수정 (무효화)

// ❌ 반복 중 push_back
std::vector<int> vec = {1, 2, 3, 4, 5};

for (auto x : vec) {
    vec.push_back(x * 2);  // ❌ 재할당 → 반복자 무효화 → 크래시
}

// ✅ 크기를 미리 저장
std::vector<int> vec = {1, 2, 3, 4, 5};
size_t size = vec.size();

for (size_t i = 0; i < size; ++i) {
    vec.push_back(vec[i] * 2);
}

에러 5: 임시 객체 순회 (C++11/14)

// ❌ C++11/14: 임시 객체 즉시 소멸
for (auto x : getVector()) {  // 임시 vector
    std::cout << x << '\n';  // ❌ 소멸된 vector 순회
}

// ✅ C++17: 수명 연장
for (auto x : getVector()) {  // OK (C++17부터)
    std::cout << x << '\n';
}

// ✅ 또는 저장
auto vec = getVector();
for (auto x : vec) {
    std::cout << x << '\n';
}

에러 6: 포인터 컨테이너

// ❌ 포인터 복사
std::vector<std::unique_ptr<int>> vec;
vec.push_back(std::make_unique<int>(42));

for (auto ptr : vec) {  // ❌ unique_ptr 복사 불가
    std::cout << *ptr << '\n';
}

// error: use of deleted function 'std::unique_ptr<int>::unique_ptr(const std::unique_ptr<int>&)'

// ✅ const 참조
for (const auto& ptr : vec) {  // 참조
    std::cout << *ptr << '\n';
}

에러 7: map 순회 실수

// ❌ 값 복사 (느림)
std::map<std::string, int> scores;

for (auto pair : scores) {  // pair 복사
    std::cout << pair.first << ": " << pair.second << '\n';
}

// ✅ const 참조
for (const auto& pair : scores) {  // 참조
    std::cout << pair.first << ": " << pair.second << '\n';
}

// ✅ 구조화 바인딩 (C++17)
for (const auto& [key, value] : scores) {
    std::cout << key << ": " << value << '\n';
}

에러 8: 배열 크기 추론 실수

// ❌ 포인터로 decay
void print(int arr[]) {  // int* 로 decay
    for (auto x : arr) {  // ❌ 포인터는 범위 기반 for 불가
        std::cout << x << '\n';
    }
}

// error: 'begin' was not declared in this scope

// ✅ 템플릿으로 크기 유지
template <size_t N>
void print(const int (&arr)[N]) {  // 배열 참조
    for (auto x : arr) {  // OK
        std::cout << x << '\n';
    }
}

// ✅ 또는 std::array
void print(const std::array<int, 5>& arr) {
    for (auto x : arr) {
        std::cout << x << '\n';
    }
}

3. 커스텀 타입 지원

begin()/end() 정의

// 커스텀 컨테이너
class MyContainer {
    std::vector<int> data_;
    
public:
    MyContainer() : data_{1, 2, 3, 4, 5} {}
    
    // 멤버 함수
    auto begin() { return data_.begin(); }
    auto end() { return data_.end(); }
    
    // const 버전
    auto begin() const { return data_.begin(); }
    auto end() const { return data_.end(); }
};

// 사용
MyContainer container;
for (auto x : container) {  // OK
    std::cout << x << '\n';
}

4. 성능 최적화

불필요한 복사 제거

// ❌ 복사 (느림)
struct BigObject {
    std::array<int, 1000> data;
};

std::vector<BigObject> vec;

for (auto obj : vec) {  // 4KB 복사
    // ...
}

// ✅ const 참조
for (const auto& obj : vec) {  // 복사 없음
    // ...
}

성능 비교

방법시간 (100만 번)
auto (복사)850ms
const auto& (참조)50ms
일반 for문50ms

분석: const auto&가 17배 빠름.


정리

범위 기반 for문 체크리스트

  • 읽기만 하면 const auto&를 사용하는가?
  • 수정하려면 auto&를 사용하는가?
  • 반복 중에 컨테이너를 수정하지 않는가?
  • 커스텀 타입은 begin()/end()가 있는가?
  • 임시 객체 순회는 C++17 이상인가?

핵심 규칙

  1. 읽기만: const auto& (복사 없음)
  2. 수정: auto& (참조)
  3. 반복 중 수정 금지 (push_back 등)
  4. 커스텀 타입은 begin()/end() 정의
  5. 임시 객체는 저장 후 순회 (C++11/14)

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

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

  • C++ 반복자 기초 | iterator 완벽 가이드
  • C++ 반복자 무효화 | iterator invalidation 해결
  • C++ 범위 기반 for문 | range-based for 가이드
  • C++ STL 알고리즘 | for_each·transform 가이드

마치며

범위 기반 for문간결하고 안전하지만, const auto&를 사용하지 않으면 불필요한 복사가 발생합니다.

핵심 원칙:

  1. 읽기만: const auto&
  2. 수정: auto&
  3. 반복 중 수정 금지
  4. 커스텀 타입은 begin()/end()

범위 기반 for문을 올바르게 사용하면 코드가 간결해지고 버그가 줄어듭니다. const auto&를 습관화하세요.

다음 단계: 범위 기반 for를 이해했다면, C++ Ranges (C++20)에서 더 강력한 범위 처리를 배워보세요.


관련 글

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