Concepts와 제약 (Concepts and Constraints)¶
concept는 아껴서 사용하세요. 일반적으로 concept와 제약은 C++20 이전이었다면 템플릿을 썼을 경우에만 사용해야 합니다. 헤더가 라이브러리 내부용으로 표시되어 있지 않다면 헤더에 새로운 concept를 도입하지 마세요. 컴파일러가 강제하지 않는 concept는 정의하지 마세요. 템플릿 메타프로그래밍보다 제약을 선호하고, template<Concept T> 문법은 피하세요. 대신 requires(Concept<T>) 문법을 사용하세요.
정의:
concept 키워드는 템플릿 매개변수에 대한 요구사항(타입 특성이나 인터페이스 명세 같은 것)을 정의하는 새로운 메커니즘입니다. requires 키워드는 템플릿에 익명의 제약을 걸고 그 제약이 컴파일 타임에 충족되는지 검증하는 메커니즘을 제공합니다. concept와 제약은 함께 쓰이는 경우가 많지만 따로 쓸 수도 있습니다.
장점:
- concept를 쓰면 템플릿이 얽힌 상황에서 컴파일러가 훨씬 나은 오류 메시지를 만들어 낼 수 있어서, 혼란이 줄고 개발 경험이 크게 나아질 수 있습니다.
- concept는 컴파일 타임 제약을 정의하고 사용하는 데 필요한 상용구를 줄여 주며, 그 결과 코드가 더 명확해지는 경우가 많습니다.
- 제약은 템플릿과 SFINAE 기법으로는 이루기 어려운 몇 가지 기능을 제공합니다.
단점:
- 템플릿과 마찬가지로 concept는 코드를 훨씬 복잡하고 이해하기 어렵게 만들 수 있습니다.
- concept는 사용하는 지점에서 클래스 타입처럼 보이기 때문에, 그 문법이 읽는 사람에게 혼란을 줄 수 있습니다.
- concept는, 특히 API 경계에서, 코드의 결합도와 경직성과 고착화를 높입니다.
- concept와 제약은 함수 본문의 로직을 그대로 옮겨 놓는 결과가 될 수 있고, 그러면 코드가 중복되고 유지보수 비용이 늘어납니다.
- concept는 그것이 표현하는 계약의 기준이 어디인지를 흐립니다. concept는 여러 곳에서 쓰일 수 있고 그 각각이 서로 따로 변해 가는, 독립적으로 이름 붙은 엔터티이기 때문입니다. 그래서 명시된 요구사항과 암묵적인 요구사항이 시간이 지나며 어긋날 수 있습니다.
- concept와 제약은 오버로드 해결에 새롭고 자명하지 않은 방식으로 영향을 줍니다.
- SFINAE가 그렇듯 제약도 코드를 대규모로 리팩터링하기 어렵게 만듭니다.
결정:
표준 라이브러리에 미리 정의된 concept에 동등한 것이 있다면 타입 특성보다 그쪽을 선호해야 합니다. (예를 들어 C++20 이전에 std::is_integral_v를 썼을 자리라면 C++20 코드에서는 std::integral을 써야 합니다.) 마찬가지로 최신 제약 문법(requires(조건))을 선호하세요. std::enable_if<조건> 같은 옛 템플릿 메타프로그래밍 구성물과 template<Concept T> 문법은 피하세요.
이미 있는 concept나 특성을 직접 다시 구현하지 마세요. 예를 들어 requires(requires { T v; }) 같은 것 대신 requires(std::default_initializable<T>)를 쓰세요.
새로운 concept 선언은 드물어야 하며, API 경계에 노출되지 않도록 라이브러리 안에서 내부적으로만 정의해야 합니다. 더 일반적으로 말하면, C++17에서 그에 해당하는 옛 템플릿을 쓰지 않았을 상황이라면 concept나 제약도 쓰지 마세요.
함수 본문을 그대로 옮겨 놓는 concept를 정의하거나, 코드 본문이나 그 결과 오류 메시지를 읽어 보면 별것 아니거나 자명한 요구사항을 부과하지 마세요. 예를 들어 아래와 같은 것은 피하세요.
template <typename T> // 나쁨 - 중복이며 얻는 것이 미미합니다
concept Addable = std::copyable<T> && requires(T a, T b) { a + b; };
template <Addable T>
T Add(T x, T y, T z) { return x + y + z; }
그 대신, 깊게 중첩되었거나 자명하지 않은 요구사항에 대한 오류 메시지처럼 그 특정 사례에서 concept가 상당한 개선을 가져온다는 것을 보일 수 있는 경우가 아니라면, 코드를 평범한 템플릿으로 두는 편을 선호하세요.
concept는 컴파일러가 정적으로 검증할 수 있어야 합니다. 주된 이점이 의미론적인(또는 그 밖에 강제되지 않는) 제약에서 나오는 concept는 쓰지 마세요. 컴파일 타임에 강제되지 않는 요구사항은 주석, 어서션, 테스트 같은 다른 수단으로 부과해야 합니다.
옮긴이 풀이¶
핵심: Concepts는 아껴서 사용¶
Concept와 제약(constraint)은 템플릿 매개변수의 요구사항을 정의하는 C++20 메커니즘입니다. 오류 메시지를 개선하는 등 장점이 있지만, 코드를 복잡하게 만들고 API 결합도를 높이므로 아껴서 쓰세요.
규칙¶
- C++20 이전에 이미 템플릿을 쓰던 경우에만 concept·제약을 도입하세요(C++17에서 레거시 템플릿을 안 썼다면 쓰지 말 것).
- 표준 라이브러리에 있는 concept를 직접 다시 구현하지 마세요. 예:
requires(requires { T v; })대신requires(std::default_initializable<T>). - 동등한 표준 concept가 있으면 타입 특성보다 그것을 선호(
std::is_integral_v→std::integral). - 최신 제약 문법
requires(조건)을 선호하고,std::enable_if<>나template<Concept T>문법은 피하세요. - 새 concept 선언은 드물게, 라이브러리 내부에만(API 경계에 노출 금지).
- 함수 본문 로직을 복제하거나, 코드를 읽으면 자명한 요구사항을 부과하는 concept는 만들지 마세요.
- concept는 컴파일러가 정적으로 검증 가능해야 합니다. 강제되지 않는 의미론적 제약이 주목적이라면 concept 대신 주석·어서션·테스트로.