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문 동작 원리
내부 변환
// 범위 기반 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 이상인가?
핵심 규칙
- 읽기만: const auto& (복사 없음)
- 수정: auto& (참조)
- 반복 중 수정 금지 (push_back 등)
- 커스텀 타입은 begin()/end() 정의
- 임시 객체는 저장 후 순회 (C++11/14)
같이 보면 좋은 글 (내부 링크)
이 주제와 연결되는 다른 글입니다.
- C++ 반복자 기초 | iterator 완벽 가이드
- C++ 반복자 무효화 | iterator invalidation 해결
- C++ 범위 기반 for문 | range-based for 가이드
- C++ STL 알고리즘 | for_each·transform 가이드
마치며
범위 기반 for문은 간결하고 안전하지만, const auto&를 사용하지 않으면 불필요한 복사가 발생합니다.
핵심 원칙:
- 읽기만: const auto&
- 수정: auto&
- 반복 중 수정 금지
- 커스텀 타입은 begin()/end()
범위 기반 for문을 올바르게 사용하면 코드가 간결해지고 버그가 줄어듭니다. const auto&를 습관화하세요.
다음 단계: 범위 기반 for를 이해했다면, C++ Ranges (C++20)에서 더 강력한 범위 처리를 배워보세요.
관련 글
- C++ 시리즈 전체 보기
- C++ Adapter Pattern 완벽 가이드 | 인터페이스 변환과 호환성
- C++ ADL |
- C++ Aggregate Initialization |