함수에서 C++ 개념을 사용하는 4가지 방법

21595 단어 conceptscpp20cpp
C++ 콘셉트 시리즈로 돌아온 것을 환영합니다.지난 글에서 우리는 개념의 배후 동기와 우리가 왜 그것을 필요로 하는지에 대해 토론했다.오늘 우리는 기존의 개념을 어떻게 사용하는지에 중점을 두고 토론할 것이다.두 가지 다른 방식이 있다.

개념을 사용하는 4가지 방법
더욱 구체적으로 말하면, 우리는 네 가지 다른 방법을 선택할 수 있다.
내가 공유하고자 하는 모든 방식에 대해 우리는 Number라는 개념을 가지고 있다고 가정하자.우리는 매우 간단한 실현을 사용할 것이다.다른 코드 세션을 시도하려면 개념을 사용할 수 있지만, 기능적 의미에서 완전하지 않다는 것을 명심하십시오.다음 회에서는 이 점을 상세하게 소개할 것이다.
#include <concepts>

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

사용requires 자구
상기 네 가지 방법 중 첫 번째 방법에서 우리는 템플릿 매개 변수 목록과 함수 반환 유형 사이에 requires 자구를 사용한다. 이 예에서는 auto이다.
template <typename T>
requires Number<T>
auto add(T a, T b) {
  return a+b;
}
우리가 이 개념을 어떻게 사용하는지 주의하십시오. 우리는 requires 자구에서 어떤 T 템플릿 매개 변수가 이 개념Number의 요구를 충족시켜야 하는지를 정의합니다.
귀환 유형을 확정하기 위해서 우리는 auto 유형 추정만 사용할 수 있지만 T 유형 추정도 사용할 수 있다.
불행히도 우리는 같은 유형의 두 숫자를 더할 수밖에 없었다.우리는 floatint를 더할 수 없다
만약 우리가 이렇게 시도한다면, 우리는 좀 길지만 쉽게 이해할 수 있는 잘못된 소식을 얻게 될 것이다.
main.cpp: In function 'int main()':
main.cpp:15:27: error: no matching function for call to 'add(int, float)'
   15 |   std::cout << add(5,42.1f) << '\n';
      |                           ^
main.cpp:10:6: note: candidate: 'template<class T>  requires  Number<T> auto add(T, T)'
   10 | auto add(T a, T b)  {
      |      ^~~
main.cpp:10:6: note:   template argument deduction/substitution failed:
main.cpp:15:27: note:   deduced conflicting types for parameter 'T' ('int' and 'float')
   15 |   std::cout << add(5,42.1f) << '\n';
      |                           ^

여러 종류의 수량을 늘리려면 두 번째 템플릿 파라미터를 도입해야 합니다.
template <typename T,
          typename U>
requires Number<T> && Number<U>
auto add(T a, U b) {
  return a+b;
}
그리고 add(1, 2.14) 이런 전화도 작용한다.개념이 수정되었습니다.단점은 모든 새로운 함수 매개 변수에 대해 새로운 템플릿 매개 변수와 그에 대한 요구를 도입해야 한다는 것이다.
리퀘스트 자구를 통해 우리는 더욱 복잡한 제약을 표현할 수 있다.예를 들어'내연'숫자의 정의를 살펴보자.
template <typename T>
requires std::integral<T> || std::floating_point<T>
auto add(T a, T b) {
  return a+b;
}
더 나은 가독성을 위해, 대부분의 경우, 나는 개념을 명명하는 것이 더 좋은 방법이라고 생각한다. 특히 당신이 더 복잡한 표현식을 가지고 있을 때.

뒤따르다requires 조항
우리는 함수 매개 변수 목록(그리고 한정자-requires, const 등과 함수가 실현되기 전에 이른바 트레이닝override 자구를 사용할 수 있다.
template <typename T>
auto add(T a, T b) requires Number<T> {
  return a+b;
}
우리는 requires 자구와 같은 결과를 얻었고, 우리는 단지 다른 의미로 그것을 썼을 뿐이다.이것은 우리가 여전히 두 가지 다른 유형의 숫자를 추가할 수 없다는 것을 의미한다.템플릿 정의를 이전처럼 수정해야 합니다.
template <typename T, typename U>
auto add(T a, U b) requires Number<T> && Number<U> {
  return a+b;
}
하지만 우리도 확장성이 있는 단점이 있다.서로 다른 유형에 속할 수 있는 모든 새 함수 매개 변수는 자신의 템플릿 매개 변수를 필요로 한다.requires 자구와 마찬가지로 뒤에 있는 requires 자구에서 더욱 복잡한 제약을 표현할 수 있습니다.
template <typename T>
auto add(T a, T b) requires std::integral<T> || std::floating_point<T> {
  return a+b;
}

구속 템플릿 매개변수
개념을 사용하는 세 번째 방법은 이전의 방법보다 간결하고 제한이 있다.
template <Number T>
auto add(T a, T b) {
  return a+b;
}
보시다시피, 저희는 requires 자문을 필요로 하지 않습니다. 템플릿 파라미터를 설명하는 곳에서 요구 사항을 정의하면 됩니다.우리는 키워드가 아닌 개념명을 사용한다. typename우리는 앞의 두 가지 방법과 같은 결과를 얻을 것이다.
만약 네가 믿지 않는다면, 내가 너에게 가서 살펴보라고 충고한다. Compiler Explorer
이와 함께 이런 방법은 한계가 있다는 점도 주목할 만하다.두 가지 방식 중 어느 하나로든 requires 자구를 사용할 때, 예를 들어 requires std::integral<T> || std::floating_point<T> 표현식을 정의할 수 있다.제약된 템플릿 매개 변수 방식을 사용할 때 이런 표현식을 사용할 수 없습니다.template <std::integral || std::floating_point T>유효하지 않습니다.
따라서 이런 방식을 통해 당신은 단일한 개념만 사용할 수 있지만 앞의 개념과 마찬가지로 더욱 간결한 형식을 사용할 수 있다.

약어 함수 템플릿
오, 깔끔하고 싶으세요?잘했어!
auto add(Number auto a, Number auto b) {
  return a+b;
}
약어 함수 템플릿을 선택할 때 템플릿 매개 변수 목록이나 requires 자구가 필요하지 않습니다.매거 함수 매개 변수의 개념을 직접 사용할 수 있다.
한 가지 주의해야 할 것이 있고, 더 많은 것을 언급해야 한다.
개념Number에 이어 우리는 auto을 뒤에 두었다.따라서 우리는 Number가 유형에 대한 구속이지 유형 자체가 아니라는 것을 알 수 있다.상상해 봐, 네가 보기만 한다면auto add(Number a, Number b).사용자로서 Number가 하나의 유형이 아니라 하나의 개념이라는 것을 어떻게 알았습니까?
내가 언급하고 싶은 또 다른 것은, 줄임말 함수 템플릿을 따를 때, 매개 변수의 유형을 혼합할 수 있다는 것이다.intfloat에 추가할 수 있습니다.
#include <concepts>
#include <iostream>

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

auto add(Number auto a, Number auto b) {
  return a+b;
}

int main() {
  std::cout << add(1, 2.5) << '\n';
}
/*
3.5
*/
따라서 약어 함수 템플릿을 사용하면 여러 개의 템플릿 파라미터를 지정하지 않은 상황에서 서로 다른 유형을 사용할 수 있다.이것은 일리가 있다. 왜냐하면 우리는 실제로 어떤 템플릿 매개 변수도 없기 때문이다.
이러한 개념을 사용하는 방법의 단점은 제약을 받는 템플릿 파라미터를 사용하는 것처럼 복잡한 표현식으로 우리의 제약을 표현할 수 없다는 것이다.

어떻게 이 네 가지 방식 중에서 선택을 진행합니까?
우리는 방금 네 가지 개념을 사용하는 방법을 보았으니 함께 그것들을 살펴보자.
#include <concepts>
#include <iostream>

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

template <typename T>
requires Number<T>
auto addRequiresClause(T a, T b) {
  return a+b;
}

template <typename T>
auto addTrailingRequiresClause(T a, T b) requires Number<T> {
  return a+b;
}

template <Number T>
auto addConstrainedTemplate(T a, T b) {
  return a+b;
}

auto addAbbreviatedFunctionTemplate(Number auto a, Number auto b) {
  return a+b;
}

int main() {
    std::cout << "addRequiresClause(1, 2): " << addRequiresClause(1, 2) << '\n';
    // std::cout << "addRequiresClause(1, 2.5): " << addRequiresClause(1, 2.5) << '\n'; // error: no matching function for call to 'addRequiresClause(int, double)'
    std::cout << "addTrailingRequiresClause(1, 2): " << addTrailingRequiresClause(1, 2) << '\n';
    // std::cout << "addTrailinRequiresClause(1, 2): " << addTrailinRequiresClause(1, 2.5) << '\n'; // error: no matching function for call to 'addTrailinRequiresClause(int, double)'
    std::cout << "addConstrainedTemplate(1, 2): " << addConstrainedTemplate(1, 2) << '\n';
    // std::cout << "addConstrainedTemplate(1, 2): " << addConstrainedTemplate(1, 2.5) << '\n'; // error: no matching function for call to 'addConstrainedTemplate(int, double)'
    std::cout << "addAbbreviatedFunctionTemplate(1, 2): " << addAbbreviatedFunctionTemplate(1, 2) << '\n';
    std::cout << "addAbbreviatedFunctionTemplate(1, 2): " << addAbbreviatedFunctionTemplate(1, 2.14) << '\n';
}
우리는 어떤 형식을 사용해야 합니까?여느 때와 마찬가지로 답은 이것이...
복잡한 요구가 있다면 표현식을 사용할 수 있도록 requires 자구나 끝부분 requires 자구가 필요합니다.
내가 말한 복잡한 수요는 무슨 뜻입니까?하나의 개념만 포함하는 것은 없다!예컨대std::integral<T> || std::floating_point<T>.이것은 제약 템플릿 매개 변수나 줄임말 템플릿 함수로 표현할 수 없습니다.
만약 당신이 여전히 그것을 사용하고 싶다면, 복잡한 구속 표현식을 자신의 개념에 추출해야 한다.
이것이 바로 우리가 개념을 정의할 때 한 것이다Number.다른 한편, 만약 개념이 여러 개의 매개 변수를 사용했다면, 제약 템플릿 매개 변수나 줄임말 템플릿 함수를 사용할 수 없거나, 적어도 나는 잠시 방법을 찾지 못했을 것이다.
만약 내가 복잡한 수요가 있다면, 나는 하나의 개념을 정의하고 명명하고 싶지 않다. 나는 앞의 두 가지 옵션 중 어느 것, 즉 withrequires 자구나 withtrainingrequires 자구를 선택할 것이다.
만약 내가 간단한 요구가 있다면, 나는 줄임말 함수 템플릿을 사용할 것이다.줄임말 함수 템플릿은 우리가 사용한 addint 호출 float 과 같이 여러 종류의 호출 함수를 동시에 사용할 수 있다는 것을 기억해야 합니다.만약 이것이 문제이고 requires 자구의 지루성을 경멸한다면, 제약을 받는 템플릿 파라미터를 선택하십시오.
우리는 우리가 토론한 것이 틀이라는 것을 또 기억해야 한다.모든 조합에 대해 컴파일러는 컴파일러를 할 때 새로운 전문화를 생성합니다.바이너리 크기나 컴파일 시간의 제한으로 템플릿 사용을 피했다면 이 점을 기억하십시오.

결론
오늘 우리는 개념과 함수 파라미터를 어떻게 결합시켜 사용하는지 이미 이해했다.우리는 네 가지 서로 다른 방법을 상세하게 소개했는데 상세한 방법일수록 구속에 있어서 우리에게 더 많은 유연성을 주었고 가장 간결한 방법(약칭 함수 템플릿)은 호출 함수의 유형에 있어서 우리에게 매우 큰 유연성을 주었다.
다음에 우리가 진정으로 자신의 개념을 작성하기 전에 표준 라이브러리에서 어떤 개념을 얻는지 토론할 것이다.
기대해주세요!
만약 당신이 C++ 개념에 대한 세부 사항을 더 알고 싶다면, check out my book on Leanpub!

좋은 웹페이지 즐겨찾기