환영합니다, Rolling Ress의 카루입니다.
오늘은 C++20에 드디어 추가된 concept에 대하여 알아보겠습니다. 템플릿 매개변수에 제약을 거는 거죠.
concept이란, 템플릿을 사용할 때 일정한 타입들을 묶어주는 기능이라고 생각하는 게 편합니다.
타입 이름에 별칭을 붙여주는 동시에, 그 자체로 bool값을 가지게 되죠.
<type_traits>에 있는 is_same_v 와 같이 ~_v 변수랑 비슷하다고 보시면 됩니다. 다만, 변수가 아니라 타입으로 쓸 수 있습니다.
선언은 이렇게 합니다.
template<typename T> //반드시 template 필요
concept 컨셉이름 = bool값의 식 또는 requires 구문
// Concepts TS 시절에는 아래와 같은 구문도 지원했으나, 이제는 지원하지 않습니다.
// concept bool 컨셉이름 = 초깃값;
일단 코드부터 보겠습니다. C++11이상에서 동작합니다.
template<typename T1, typename T2,
typename = typename enable_if<is_convertible<T1, int>::value
&& is_convertible<T2, int>::value>::type>
long long int add_int(T1 t1, T2 t2)
{
return {t1 + t2};
}
C++11부터인가 아마 사용 가능할 거예요. 매개변수가 둘 다 int로 변환 가능하면 더해서 long long으로 반환하는 간단한 함수이죠. 단 변환 불가능하면 컴파일이 되지 않습니다. 이를 위해 enable_if를 사용하죠.
enable_if<bool>::type에서 조건식이 false이면 type이 존재하지 않습니다. 그래서 컴파일이 안돼요.
typename = typename 이부분의 첫 typename은 템플릿 선언이고 '='은 기본 타입 지정, 뒤의 typename은 enable_if<bool>::type이 타입 이름이라는 걸 알려주는 역할을 합니다. 같은 typename이지만 용도가 달라요.
근데 너무 길어요. 줄입시다. C++17 이상에서 동작합니다.
template<typename T1, typename T2,
typename = enable_if_t<is_convertible_v<T1, int>
&& is_convertible_v<T2, int>>>
long long add_int(T1 t1, T2 t2)
{
return {t1 + t2};
}
typename enable_if<>::type을 enable_if_t로, is_convertible<.>::value를 is_convertible_v<.>로 줄였습니다. 보기 좀 더 편하네요. _v 는 <type_trais>에 있는 대부분의 값에 사용할 수 있습니다. is_same_v처럼요. 아마 이것도 inline 변수가 아닌가 싶은데,
하지만 여전히 길어요. 장난합니까? 함수 몸체는 한 줄인데 템플릿이 두 줄?
그래서 Contracts가 도입될 예정이었습니다.
template<typename T1, typename T2>
[[expects: is_convertible_v<T1, int> && is_convertible_v<T2, int>]]
long long add_int(T1 t1, T2 t2)
{
return {t1 + t2};
}
그러나 무슨 이유에서인지 C++20에서도 Contracts는 도입되지 않는다고 합니다.
대신, 이걸 우리는 Concepts로 해결할 수 있습니다. 그나마 다행이죠.
// concept 선언. int로 변환 가능한 T는 Integer 컨셉을 만족시킨다.
template<typename T>
concept Integer = is_convertible_v<T, int>;
이렇게 하면 Integer이라는 컨셉이 생성됩니다. bool 변수에요. 동시에 하나의 타입이기도 합니다.
template<typename T1, typename T2> requires Integer<T1> && Integer<T2>
long long add_int(T1 t1, T2 t2)
{
return {t1 + t2};
}
함수 선언부입니다. 이렇게 requires 뒤에 템플릿 인자가 요구하는 컨셉을 적으면 됩니다.
template<typename T1, typename T2>
long long add_int(T1 t1, T2 t2) requires Integer<T1> && Integer<T2>
{
return {t1 + t2};
}
함수 머리 바로 뒤로 보내도 작동합니다, 이 경우엔 C# 제네릭의 where과 비슷한 역할을 하죠.
long long add_int(Integer auto t1, Integer auto t2)
{
return {t1 + t2};
}
// Concepts TS 시절에는 아래와 같은 구문을 지원했으나, 정식으론 지원하지 않습니다.
// long long add_int(Integer t1, Integer t2)
심지어는 이렇게도 가능합니다! Concepts TS에는 auto가 없어도 되는데, C++20에는 auto나 decltype(auto)를 반드시 사용하라고 하네요.
자, 이번엔 TS에서 나온 Concepts에 대해 알아봅시다.
#include <iostream> // 다들 아시죠?
#include <type_traits>
using namespace std;
template<typename T>
concept Integer = is_convertible_v<T, int>;
Integer{T}
T add_int(T t1, T t2)
{
return t1 + t2;
}
decltype(auto) getval(auto s)
{
return s;
}
int main()
{
[[maybe_unused]] Integer auto x = getval(3);
cout << add_int(int{1}, int{2}) << endl;
}
이건 T라는 Integer 타입을 선언하면서, 매개변수의 타입이 같을 때에만 컴파일 해 줍니다.
main()을 바꿔볼까요.
int main() {
auto var1 = getval(string{"HAHAHAHA"});
cout << "var1: " << var1 << endl;
auto var2 = getval(3U);
cout << "var2: " << var2 << endl;
auto var3 = getval("JAJA GGHS");
cout << "var3: " << var3 << endl;
return 0;
}
getval()은 받은 것을 그대로 반환하는 함수입니다. 출력 결과는 따라서
var1: HAHAHAHA
var2: 3
var3: JAJA GGHS
자, 그런데 auto는 아무 타입이나 다 정해버리죠. 그렇다면 Integer만 받고 싶다면 어떻게 할까?
아까 그 컨셉을 이용하면 됩니다.
auto를 모두 Integer로 바꾸어 컴파일 하면
In function 'int main()':
error: deduced initializer does not satisfy placeholder constraints
Integer var1 = getval(string{"HAHAHAHA"});
note: constraints not satisfied
이야..똑똑합니다. var1과 var3를 주석처리 하겠습니다.
var2: 3
성공적으로 타입 추론이 됩니다. 이로써 auto 대신 컨셉을 통해 타입 추론도 할 수 있죠.
// C++20 이전, 타입 추론의 제한
auto var2 = getval(3U);
static_assert(is_convertible_v<decltype(var2), int>,
"ERROR: getval() requires integral type");
// Concepts TS / C++20에서의 타입 추론 제한
Integer var2 = getval(3U); // Concepts TS
Integer auto var2 = getval(3U); // C++20 Stnadard
윗 세 줄은 현재 사용 가능한 방법이고, 아랫줄은 Concepts TS 또는 C++20 표준안에서 사용 가능한 방법입니다.
Concepts에 대한 포스팅을 한 번 더 하게 된다면 requires에 대해 더 자세히 알아보겠습니다.
typename 처럼 뜻이 두 개가 있거든요.
'C++ > C++ 특수 강좌 & 모던 C++' 카테고리의 다른 글
[C++20 특수 강좌] 선택문 최적화를 위한 [[likely]], [[unlikely]] (0) | 2022.02.16 |
---|---|
C++ 캐스팅 연산자 성능 비교 (0) | 2022.02.16 |
[C++20 특수 강좌] constexpr을 넘어선 consteval (0) | 2022.02.16 |
[C++심화 강좌] C++20 모듈 사용하기 (0) | 2021.05.15 |