본문 바로가기
c++/모던c++

c++20(concept)

by kcj3054 2022. 5. 23.

concpet란?

  • concept는 c++20에서 추가된 것이다. 이것을 사용하는 이유는 몇가지 있는데 대표적으로 template을 사용할 때 에러코드는 길어져서 읽기가 힘든 것이있다. 그렇지만 concept를 이용해서 제약조건을 준다면 에러코드가 명확해지면서 짧아지게된다.
  • 또한 requires절과 함수오버로딩을 섞을 수 있다 -> 함수 오버로딩을 하는데 만약 제약조건에서 가상함수 일 경우는 함수a를 사용하고 그렇지않으면 함수 b를 사용하게 하는 경우 적용할 수 있다..
  • concept은 --- > a named set of requirements이다.. 이것은 요구조건들의 집합..! 조건을 하나가 아니라 여러개를 둘 수 있다는 의미이다.

기본 문법 예제

template<typename T>
concept GreaterThan4 = sizeof(T) >= 4;

template<typename T> requires GreaterThan4<T>
void foo(T arg)
{

}

//===최대공약수 concept..
//required절을 사용하지 않으면.. 만약 a % b부분이 compile 에러가 발생하는데, 이러한 줄이 여러개라면 오류가 엄청 길어진다..
template<typename T>
T gcd1(T a, T b)
{
    return b == 0 ? a : gcd1(b, a % b);
}

// requires 절을 사용해서 gcd2 함수를 찾을 수 없다는 에러를 말한다.. 
template<typename T> requires std::is_integral_v<T>
T gcd2(T a, T b)
{
    return b == 0 ? a : gcd2(b, a % b);
}

int main()
{
    int i = 1;

    foo(i);

    gcd1(4.2, 2.1); //error 
}
  • 위에서 concept GreaterThan4 = sizeof(T) >= 4; 이라고 GreaterThan4는 크기가 4이상인 것이다. 이것을 이용해서 제약조건을 줄 수 있다.
  • 밑의 foo함수에서 requires 뒤에 제약 조건이 concept으로 만든 GraterThan4이다..
  • 그리고 gcd함수 두개를 보면 에러코드가 짧아진다는 예를 확인할 수 있다..

requires clauses과 함수 오버로딩.. (가상함수버전 적용)

class Test
{
public:
    virtual void foo() {}
};

template<typename T> requires std::is_polymorphic_v<T>
void foo(const T& arg)
{
    cout << "가상함수 버전" << endl;
}

template<typename T> requires (!std::is_polymorphic_v<T>)
void foo(const T& arg)
{
    cout << "No 가상함수 버전" << endl;
}

int main()
{
    foo(Test());
}
  • 또한 requires 절을 이용할 때 type_traits를 같이 사용하면 제약조건을 주기가 좋은데, 위에서 std::is_polymorphic_v를 사용하면 T가 가상함수인지 아닌지 파악이 가능하다...
  • 참고로 requires 절에는 bool값으로 파악이 가능한 상수가 가능하다.. !!

requires clauses과 함수 오버로딩.. (iteraotr 임의접근, 순차접근 적용)

  template<typename T> requires std::random_access_iterator<T>
void advance(T& p, int n)
{
    cout << "임의 접근" << endl;
    p = p + n;
}

template<typename T> requires std::input_iterator<T>
void advance(T& p, int n)
{
    cout << "임의 접근이 아닌 경우" << endl;
    while (n--) ++p;
}

int main()
{
    std::vector c = { 1, 2, 3, 4,  5, 6, 7 }; //vector는 임의접근이랑 input_iterator 둘다 가능 
    std::list cc = { 1, 2, 3, 4,  5, 6, 7 }; //input_iterator만 가능.. 

    auto p = std::begin(c);

    advance(p, 5);

    return 0;
}
  • 위에서 requires절을 이용한 함수오버로딩을 볼 수 있다. 여기서 std::input_iterator 와 std::random_access_iterator도 type_trais에 있는 것이다.. !

concept의 a named set of requirements 예시.

template<typename T>
concept Integral = std::is_integral_v<T>;

//concept require절을 사용할때 template파라미터 옆에 사용해도 되지만 foo(T a) 옆에 사용해도된다.. ! 
template<typename T> requires Integral<T>
void foo(T a)
{

}
  • 위에서 컨셉을 정의하였다. 컨셉의 이름은 Integral 이것은 정수만 받을 수 있다...
  • 여러개의 제약 조건을 하나의 이름으로 표현가능하다 std::is_integral_v 뒤에 &&로 늘어놔도 동일하다...

requires_expression

  template<typename T>  concept Integral = std::is_integral_v<T>;
template<typename T> concept True = true;

// requires expression -> concept를 만들 때 사용하는 표현식 ! 
template<typename T>
concept Modulus = requires(T a, T b)
{
    a% b;
};

//requires clauses requires뒤에 제약조건을 준다.. 
template<typename T> requires Modulus<T>
T Mod(T a, T b)
{
        return a % b;
}

int main()
{
    Mod(10, 3);
}
  • 위에서 requires_expression을 보여주고있다.
  • requires_expression는 concept을 만들 때 사용하면 표현식이다.
  • 위에서 Modulus는 임의의 타입 a, b가 %가 가능해ㅑㅇ한다는 것이다.

requires_expression2

requires_expression의 기본 문법..

requires { requirement-seq};,

 requires (parameter-list) {requirement_seq};...
  • requires 후에 바로 문맥이 와도 되고 아니면, parameter-list가 있어도된다...

  • 다양한 예시를 보자.

template<typename T>
concept LessThanComparable1 = requires(T a, T b)
{
    a < b;
};

template<typename T>
concept LessThanComparable2 = requires(T a, T b)
{
    {a < b} -> std::convertible_to<bool>; // convertible_to는 concept안에 있어야한다.. , {a < b} 결과가 <bool>로 변환이 가능해야한다.. 
};

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

//Container라는 concept을 만드는데 그것은 begin()이랑 end()를  사용할 수 있어야한다.. 
template<typename T>
concept Container = requires(T c)
{
    c.begin();
    c.end();
};

template<typename T>
concept HasValueType = requires
{
    typename T::value_type;
};

template<typename T> requires Container<T>
void foo(T a) {}

template<typename T> requires HasValueType<T>
void goo(T a) {}

int main()
{
    vector<int> v = { 1, 2, 3 };

    foo(v);
    goo(v);

    // foo(1);
    //goo(1);
    return 0;
}
  • 위에서 LessThanComparable1는 requires가 오고 뒤에 파라미터가 온 뒤 해당 변수들이 a < b를 만족해야한다는 표현식이다..
  • LessThanComparable2에는 convertible_to가 사용되었다.. 이것은 #include 안에 존재한다... 뜻은 {a < b} 결과가 로 변환이 가능해야한다..