[C++20 새기능 소개] 개념 (Concepts)

C++20의 새로운 기능들을 소개하는 시리즈의 두 번째 글에 오신 것을 환영합니다. 이번 글에서는 템플릿 프로그래밍을 더욱 강력하고 유연하게 만들어 줄 개념(Concepts)에 대해 자세히 알아보겠습니다.

Concepts란 무엇인가요?

Concepts는 C++20에서 도입된 기능으로, 템플릿 매개변수에 대한 제약 사항을 명확하고 간결하게 표현할 수 있게 해주는 도구입니다. 이를 통해 템플릿 코드를 더욱 안전하고 이해하기 쉽게 만들 수 있습니다.

왜 Concepts를 사용해야 할까요?

기존의 템플릿 코드는 컴파일 타임 에러 메시지가 난해하고 길어서 디버깅이 어려웠습니다. Concepts를 사용하면 템플릿 매개변수에 대한 명확한 제약을 지정할 수 있어, 컴파일 타임 에러를 줄이고 더 이해하기 쉬운 에러 메시지를 제공할 수 있습니다.

간단한 예제

기존의 템플릿 코드

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

위의 add 함수는 + 연산이 가능한 모든 타입에 대해 동작합니다. 하지만 + 연산이 불가능한 타입을 전달하면 복잡한 컴파일 에러 메시지가 발생합니다.

Concepts를 사용한 개선

#include <concepts>

template <typename T>
requires std::integral<T> || std::floating_point<T>
T add(T a, T b) {
    return a + b;
}

또는 더 간결하게:

template <std::integral T>
T add(T a, T b) {
    return a + b;
}

위 코드에서 std::integral과 std::floating_point는 C++20에서 제공하는 Concepts로, 각각 정수형과 부동소수점 타입을 의미합니다. 이제 add 함수는 정수나 부동소수점 타입에 대해서만 동작하며, 다른 타입을 전달하면 명확한 컴파일 에러가 발생합니다.

Concepts의 사용 방법

1. 표준 라이브러리의 Concepts 사용

C++20은 표준 라이브러리에 여러 가지 기본적인 Concepts를 제공합니다.

  • std::integral: 정수형 타입
  • std::floating_point: 부동소수점 타입
  • std::same_as<T, U>: 타입 T와 U가 동일한지 확인
  • std::assignable_from<T, U>: T에 U를 할당할 수 있는지 확인

예를 들어:

#include <concepts>

template <std::integral T>
void doSomething(T value) {
    // 정수형에 대해서만 동작
}

2. 사용자 정의 Concepts

사용자 정의 Concepts를 만들어서 보다 구체적인 제약 사항을 정의할 수 있습니다.

template <typename T>
concept Addable = requires(T a, T b) {
    { a + b } -> std::convertible_to<T>;
};

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

위 코드에서 Addable이라는 Concept을 정의하여 + 연산이 가능한 타입을 표현했습니다.

Concepts를 사용한 함수 오버로딩

Concepts를 활용하면 함수 오버로딩을 더욱 직관적으로 구현할 수 있습니다.

#include <concepts>
#include <iostream>

void printValue(std::integral auto value) {
    std::cout << "정수 값: " << value << std::endl;
}

void printValue(std::floating_point auto value) {
    std::cout << "부동소수점 값: " << value << std::endl;
}

int main() {
    printValue(42);       // 정수 값: 42
    printValue(3.14);     // 부동소수점 값: 3.14
    return 0;
}

Concepts를 사용한 클래스 템플릿

클래스 템플릿에서도 Concepts를 사용할 수 있습니다.

#include <concepts>
#include <vector>

template <typename T>
requires std::integral<T>
class IntegerVector {
public:
    void add(T value) {
        data.push_back(value);
    }
private:
    std::vector<T> data;
};

위 클래스는 정수형 타입에 대해서만 인스턴스화될 수 있습니다.

Concept의 논리 연산자

Concepts는 논리 연산자를 통해 결합할 수 있습니다.

  • &&: 논리 AND
  • ||: 논리 OR
  • !: 논리 NOT

예를 들어:

template <typename T>
concept Number = std::integral<T> || std::floating_point<T>;

자세한 예제: 정렬 가능한 타입

#include <concepts>
#include <vector>
#include <algorithm>

template <typename T>
concept Sortable = requires(T a) {
    std::sort(a.begin(), a.end());
};

void sortContainer(Sortable auto& container) {
    std::sort(container.begin(), container.end());
}

int main() {
    std::vector<int> vec = {3, 1, 4, 1, 5};
    sortContainer(vec);

    for (auto v : vec) {
        std::cout << v << " ";
    }
    // 출력: 1 1 3 4 5

    return 0;
}

위 코드에서 Sortable이라는 Concept을 정의하여 std::sort를 사용할 수 있는 컨테이너를 표현했습니다.

Concepts의 장점

  • 가독성 향상: 템플릿 매개변수의 제약 사항을 명확하게 표현합니다.
  • 에러 메시지 개선: 컴파일 타임 에러 메시지가 더 명확해집니다.
  • 코드 안정성 증가: 부적절한 타입 사용을 컴파일 타임에 방지합니다.

결론

C++20의 Concepts를 사용하면 템플릿 프로그래밍을 더욱 강력하고 안전하게 수행할 수 있습니다. 이는 코드의 가독성을 높이고, 디버깅 시간을 줄여줍니다.

반응형