C++ 초급 - 12. 최신 C++ 기능 소개 (4 - C++20: concepts와 범위 기반 for 개선)

2025. 2. 22. 18:16프로그래밍 언어/C++

📌 12.4 C++20: concepts와 범위 기반 for 개선

C++20은 모던 C++에서 가장 큰 변화 중 하나로, 템플릿 타입 제약을 위한 concepts, 향상된 범위 기반 for 루프, 코루틴 및 ranges 라이브러리를 제공한다.
이를 통해 코드를 더욱 직관적이고 최적화할 수 있으며, 컴파일 타임 안정성이 향상된다.


📌 1. 템플릿에서 concepts를 활용한 타입 제약

🔹 (1) 기존 템플릿의 문제점

C++17까지는 템플릿에서 타입을 제한할 수 없어, 잘못된 타입이 들어와도 컴파일 에러 메시지가 모호했다.

💡 예제: 기존 C++17 템플릿 (제약 없음)

#include <iostream>

template <typename T>
T add(T a, T b) {
    return a + b;
}

int main() {
    std::cout << add(3, 5) << std::endl;       // 정상 동작
    std::cout << add("Hello, ", "World!");    // 컴파일 오류 (문자열 덧셈 불가)
    return 0;
}

🔹 문제점

  • add("Hello", "World")는 문자열 포인터(char*)를 더할 수 없어 컴파일 에러 발생.
  • 에러 메시지가 모호하여 원인을 찾기 어려움.

🔹 (2) C++20 concepts를 활용한 타입 제약

C++20에서는 concepts를 도입하여, 템플릿 타입을 제약할 수 있음.
이를 통해 템플릿의 타입 검사를 컴파일 타임에 수행할 수 있으며, 가독성이 좋아짐.

💡 예제: concepts를 활용한 타입 제약

#include <iostream>
#include <concepts>

// 정수형 타입만 허용하는 개념 정의
template <typename T>
concept Numeric = std::integral<T>;

template <Numeric T>
T add(T a, T b) {
    return a + b;
}

int main() {
    std::cout << add(3, 5) << std::endl;      // 정상 동작
    // std::cout << add(3.5, 2.1);           // 컴파일 오류 (정수형만 허용)
    return 0;
}

🔹 출력 결과

8

💡 설명

  • concept Numeric = std::integral<T>; → std::integral을 이용해 정수형만 허용하도록 제한.
  • template <Numeric T> → 템플릿에서 Numeric을 적용하여 정수형 타입만 허용.
  • 컴파일 타임에 타입 검사가 수행되어, 부적절한 타입은 즉시 에러 발생.

🔹 (3) requires 절을 활용한 concepts

C++20에서는 requires를 활용하여 더 유연하게 타입 제약 가능.

💡 예제: requires를 활용한 타입 제약

#include <iostream>
#include <concepts>

template <typename T>
requires std::integral<T>  // 정수 타입만 허용
T multiply(T a, T b) {
    return a * b;
}

int main() {
    std::cout << multiply(4, 6) << std::endl;  // 정상 동작
    // std::cout << multiply(2.5, 3.1);       // 컴파일 오류 (정수만 허용)
    return 0;
}

🔹 출력 결과

24

💡 설명

  • requires std::integral<T>를 사용하여 정수 타입만 허용.
  • 가독성이 높아지고, 오류 메시지가 더 명확해짐.

📌 2. 향상된 범위 기반 for 루프 사용법

🔹 (1) 기존 for 루프의 문제점

C++17까지는 범위 기반 for 루프에서 std::vector<bool>을 올바르게 순회하기 어려웠다.

💡 예제: 기존 C++17의 std::vector<bool> 문제

#include <iostream>
#include <vector>

int main() {
    std::vector<bool> flags = {true, false, true};

    // 오류 발생 가능 (vector<bool>은 프록시 객체를 반환)
    for (bool flag : flags) {
        std::cout << flag << " ";
    }

    return 0;
}

🔹 문제점

  • std::vector<bool>은 프록시 객체(proxy object)를 반환하여, 값이 의도와 다르게 동작할 수 있음.

🔹 (2) C++20의 향상된 범위 기반 for 루프

C++20에서는 범위 기반 for 루프가 std::vector<bool>을 올바르게 처리할 수 있도록 개선되었다.

💡 예제: C++20에서 std::vector<bool>을 올바르게 처리

#include <iostream>
#include <vector>

int main() {
    std::vector<bool> flags = {true, false, true};

    for (bool flag : flags) {  // 안전하게 순회 가능
        std::cout << flag << " ";
    }

    return 0;
}

🔹 출력 결과

1 0 1

💡 설명

  • C++20부터 std::vector<bool>도 안전하게 범위 기반 for 루프에서 사용 가능.

📌 3. 기본적인 문법 개선 (코루틴, ranges 등)

C++20에서는 코루틴(Coroutines)과 ranges 라이브러리가 추가되어 코드가 더욱 간결해졌다.

🔹 (1) 코루틴 (Coroutines) 개요

  • C++20의 코루틴은 함수 실행을 중단하고 다시 시작할 수 있도록 지원.
  • 비동기 프로그래밍과 효율적인 제너레이터(generator) 구현이 가능.

💡 예제: C++20 코루틴을 활용한 숫자 생성기

#include <iostream>
#include <coroutine>

struct Generator {
    struct promise_type {
        int current_value;
        std::suspend_always yield_value(int value) {
            current_value = value;
            return {};
        }
        Generator get_return_object() { return Generator{this}; }
        std::suspend_always initial_suspend() { return {}; }
        std::suspend_always final_suspend() noexcept { return {}; }
        void return_void() {}
        void unhandled_exception() { std::terminate(); }
    };

    using handle_type = std::coroutine_handle<promise_type>;
    handle_type handle;

    Generator(promise_type* p) : handle(handle_type::from_promise(*p)) {}
    ~Generator() { handle.destroy(); }

    bool next() {
        if (!handle.done()) {
            handle.resume();
            return true;
        }
        return false;
    }

    int getValue() { return handle.promise().current_value; }
};

Generator counter() {
    for (int i = 1; i <= 5; i++) {
        co_yield i;
    }
}

int main() {
    auto gen = counter();
    while (gen.next()) {
        std::cout << gen.getValue() << " ";
    }
    return 0;
}

🔹 출력 결과

1 2 3 4 5

💡 설명

  • co_yield를 사용하여 중간 값을 반환하고 실행을 일시 중지.
  • 다음 값을 요청하면 중단된 위치에서 실행을 재개.

📌 4. 정리

개념  설명
concepts 템플릿에서 타입을 제한하여 컴파일 타임 오류를 줄임
requires 절 특정 조건을 만족하는 타입만 템플릿에 사용할 수 있도록 제약
향상된 범위 기반 for std::vector<bool>을 포함한 컨테이너를 안전하게 순회 가능
코루틴 co_yield, co_await, co_return을 통해 비동기 프로그래밍 지원