어떻게 자신의 개념을 편찬합니까?제1부.

28611 단어 conceptscpp20cpp
몇 주 전에 우리는 C++ 개념의 배후 동기와 함수와 클래스에서 그것을 어떻게 사용하는지 토론했다.그러나 우리는 거의 써 본 적이 없다.예를 들어 우리는 기능적으로 불완전한 개념인 Number을 정의했지만 그것뿐이었다.이제 우리는 우리가 개념에서 어떤 구속을 표현할 수 있는지 상세하게 토론할 것이다.
만약 내가 서로 다른 종류의 제약을 동시에 포함한다면, 이 문장은 너무 길다.이 글에서 우리는 간단한 개념부터 시작하여 기존의 개념을 결합한 다음에 필요한 조작과 클래스 API에 대한 일반적인 요구를 완성할 것이다.
다음 주에, 나는 당신에게 되돌아오는 유형의 수요를 어떻게 작성하고, 유형의 수요를 어떻게 표현하며, 제약을 어떻게 끼워 넣는지 보여 줄 것입니다.
이제 시작할 때가 됐어.

simples concept우리 먼저 가장 간단한 개념을 정의하고 문법을 봅시다.
template<typename T> 
concept Any = true;
우선, 우리는 템플릿 파라미터를 열거합니다. 이 예에서 우리는 T만 있지만, 쉼표로 구분된 파라미터가 여러 개 있을 수 있습니다.그리고 키워드 concept, 다음에 우리는 개념의 명칭을 성명하고 = 다음에 우리는 개념을 정의한다.
본 예에서 우리는 true만 말한다. 이것은 어떤 유형의 T에 대해서도 개념이 true으로 평가된다는 것을 의미한다.모든 유형이 가능합니다.만약 우리가 false을 쓴다면 아무것도 받아들여지지 않을 것이다.
현재 우리는 가장 간단한 개념을 보았으니 우리가 어떤 구축 블록을 사용하여 더욱 상세한 개념을 구축할 수 있는지 살펴보자.

정의된 개념 사용
새로운 개념을 정의하는 가장 간단한 방법은 기존 개념을 결합시키는 것이라고 할 수 있다.
예를 들어 다음 예에서 우리는 정수와 부동점수를 받아들여 Number이라는 개념을 다시 만들 것이다.
#include <concepts>

template<typename T> 
concept Number = std::integral<T> || std::floating_point<T>;
위의 예에서 보듯이 우리는 || 연산자와 두 개념을 쉽게 결합시킬 수 있다.물론 우리는 어떤 논리 연산자도 사용할 수 있다.
이것은 말하지 않아도 알 수 있지만, 우리도 사용자 정의의 개념을 사용할 수 있다.
#include <concepts>

template<typename T> 
concept Integer = std::integral<T>;

template<typename T> 
concept Float = std::floating_point<T>;

template<typename T> 
concept Number = Integer<T> || Float<T>;
본 예에서 우리는 기본적으로 std::integralstd::floating_point에 대해 별명(간접 주소 지정)을 실시하여 사용자가 정의한 개념도 개념의 조합에 사용할 수 있음을 나타냈다.
As we saw earlier, 표준 라이브러리의 서로 다른 제목에 대량의 개념을 정의했기 때문에 끝없는 방식으로 그것들을 결합시켰다.
그러나 어떻게 진정으로 독특한 개념을 정의합니까?

자신의 제약조건 작성
다음 장에서 우리는 어떠한 사전 정의 개념도 사용하지 않고 어떻게 우리 자신의 독특한 수요를 표현할 수 있는지 깊이 있게 연구할 것이다.

운영 요구 사항
우리는 템플릿 파라미터가 어떤 조작이나 연산자를 지원하기를 희망한다는 것을 간단하게 나타낼 수 있다.
템플릿 매개 변수를 추가할 수 있도록 요구하는 경우 다음과 같은 개념을 만들 수 있습니다.
#include <iostream>
#include <concepts>

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

auto add(Addable auto x, Addable auto y) {
  return x + y;
}

struct WrappedInt {
  int m_int;
};

int main () {
  std::cout << add(4, 5) << '\n';
  std::cout << add(true, true) << '\n';
  // std::cout << add(WrappedInt{4}, WrappedInt{5}) << '\n'; // error: use of function 'auto add(auto:11, auto:12) [with auto:11 = WrappedInt; auto:12 = WrappedInt]' with unsatisfied constraints
}
/*
9
2 
*/
add()의 매개 변수를 사용하여 WrappedInt을 호출할 때 (operator+을 지원하지 않기 때문에) 컴파일에 실패하고 묘사적인 오류 메시지가 발생했습니다. (전체 오류 메시지가 상기 예시로 복사된 것은 아닙니다.)Addable 개념을 작성하는 것은 상당히 쉬운 것 같다, 그렇지?requires 키워드 이후, 우리는 기본적으로 우리가 번역하고 실행하기를 원하는 문법을 썼다.

인터페이스에 대한 간단한 요구
우리 운영을 다시 한번 고려해 봅시다.+ 운영을 지원해야 한다는 것은 무엇을 의미합니까?
이것은 우리가 받아들일 유형 제약이 T T::operator+(const T& other) const 함수를 가진 유형이라는 것을 의미한다.또는 T T::operator+(const U& other) const일 수도 있다. 왜냐하면 우리는 다른 유형의 실례에 추가하고 싶을 수도 있지만, 이것은 중점이 아니다.나의 관점은 우리가 특정한 기능을 가지는 것에 대해 요구를 제기했다는 것이다.
그래서 우리는 어떤 함수 호출의 요구를 정의할 수 있어야 하지 않겠는가?
네, 어떻게 하는지 보여 주세요.
#include <iostream>
#include <string>
#include <concepts>

template <typename T> // 2
concept HasSquare = requires (T t) {
    t.square();
};

class IntWithoutSquare {
public:
  IntWithoutSquare(int num) : m_num(num) {}
private:
  int m_num;
};

class IntWithSquare {
public:
  IntWithSquare(int num) : m_num(num) {}
  int square() {
    return m_num * m_num;
  }
private:
  int m_num;
};


void printSquare(HasSquare auto number) { // 1
  std::cout << number.square() << '\n';
}

int main() {
  printSquare(IntWithoutSquare{4}); // error: use of function 'void printSquare(auto:11) [with auto:11 = IntWithoutSquare]' with unsatisfied constraints, 
                                    // the required expression 't.square()' is invalid
  printSquare(IntWithSquare{5});
}
이 예에서 우리는 함수 printSquare(1)이 하나 있는데 개념을 만족시키는 HasSquare(2)의 매개 변수가 필요하다.이 개념에서 우리는 우리가 기대하는 인터페이스를 정의하는 것이 매우 쉽다는 것을 알 수 있다.requires 키워드 다음에, 우리는 수용 유형의 인터페이스가 어떤 호출을 지원해야 하는지 적어야 한다.
우리의 기대는 requires 키워드 뒤에 쓰여 있다.우선, 괄호 사이에는 함수와 같은 매개 변수 목록이 있습니다. 우리는 제약을 받을 템플릿 매개 변수와 제약에 나타날 수 있는 다른 매개 변수를 모두 열거해야 합니다.이따가 다시 이야기합시다.
만약 우리가 모든 전송 유형에 square이라는 함수가 있기를 원한다면, 우리는 (T t) {t.square();}을 작성하기만 하면 된다.(T t), 우리는 T 템플릿 유형의 실례에 제약을 정의하고 싶기 때문이다. t.square(), 왜냐하면 우리는 t 템플릿 유형의 T 실례에 반드시 공공 함수 square()이 있기를 희망하기 때문이다.
만약 우리가 여러 함수 호출의 유효성에 대해 요구가 있다면, 우리는 모든 함수를 열거하고, 분호로 나누기만 하면 된다. 우리가 하나하나 호출하는 것처럼.
template <typename T>
concept HasSquare = requires (T t) {
  t.square();
  t.sqrt();
};
매개 변수는?power 함수를 정의하겠습니다. 이 함수는 int 매개 변수를 지수로 합니다.
template <typename T>
concept HasPower = requires (T t, int exponent) {
    t.power(exponent);
};

// ...

void printPower(HasPower auto number) {
  std::cout << number.power(3) << '\n';
}
exponent 함수에 전달되는 T::power 변수는 requires 키워드와 그 유형 다음에 우리가 제약하는 템플릿 유형에 열거해야 한다.따라서, 우리가 수정한 매개 변수는 (전환 가능) int이 될 것이다.
하지만 우리가 그 어떤 정수도 지수로 받아들이고 싶다면요?뜻이 있는 곳에 길이 있다!문법 문제와 관련이 있을 때, 이것은 항상 정확한 것은 아니지만, 이런 상황에서 우리는 매우 운이 좋다.
우선, 우리의 개념은 HasPower에 두 개의 매개 변수를 포함해야 한다.하나는 기본 형식에, 하나는 지수 형식에 사용합니다.
template <typename Base, typename Exponent>
concept HasPower = std::integral<Exponent> && requires (Base base, Exponent exponent) { 
    base.power(exponent);
};
우리는 템플릿 유형 Exponent이 정수이고 이를 매개 변수로 Base::power()에 전달할 수 있도록 확보했다.
다음 단계는 printPower 함수를 업데이트하는 것입니다.HasPower의 개념은 이미 바뀌었고 현재는 두 가지 유형이 있기 때문에 우리는 상응하는 변화를 해야 한다.
template<typename Exponent>
void printPower(HasPower<Exponent> auto number, Exponent exponent) {
  std::cout << number.power(exponent) << '\n';
}
Exponent은 템플릿 형식의 매개 변수로 표시되기 때문에 그 뒤에 auto 키워드가 필요하지 않습니다.한편, auto 이후 HasPower이 필요합니다. 그렇지 않으면 특정한 유형이 아닌 개념임을 어떻게 알 수 있겠습니까?!Exponent이 템플릿 유형의 매개 변수로 HasPower에 전달될 때도 이를 적용한다.
이제 printPower을 다음과 같이 호출할 수 있습니다. API 변경 후 IntWithSquareIntWithPower으로 변경한 것을 고려하면 다음과 같습니다.
printPower(IntWithPower{5}, 3);
printPower(IntWithPower{5}, 4L);
또한 printPower(IntWithPower{5}, 3.0);을 호출하면 실패합니다. float 유형이 완전성 제약에 부합되지 않기 때문입니다.
저희가 뭘 놓쳤나요?맞아요!우리는 IntWithPower을 지수로 사용할 수 없다.우리는 사용자 정의 유형을 사용하여 Base::power(Exponent exp), 예를 들어 IntWithPower을 호출할 수 있기를 희망한다. 이를 위해 우리는 두 가지가 필요하다.
  • IntWithPowerintegral
  • 으로 간주해야 한다
  • IntWithPowerpow에서 cmath의 제목에서 받아들인 내용으로 전환할 수 있어야 한다.
  • 우리 한 명씩 갑시다.type_trait을 명시적으로 지정하여 std::is_integral IntWithPower을 정수 유형으로 만들 수 있습니다.물론 우리가 현실 생활에서 이렇게 할 계획이라면, 우리의 유형이 정형화된 모든 특징을 가지고 있다는 것을 확보하는 것이 가장 좋으나, 이것은 우리의 범위를 넘어선 것이다.
    template<>
    struct std::is_integral<IntWithPower> : public std::integral_constant<bool, true> {};
    
    현재 우리는 IntWithPower IntWithPower 으로 받아들일 수 있는 유형으로 전환할 수 있도록 확보해야 한다.이것은 부동 소수점 유형을 받아들이지만, 내가 보기에 pow과 관련될 때, 이를 IntWithPower으로 변환하고 컴파일러가 int의 은식 변환을 실행하도록 하는 것이 더 의미가 있다. 비록 일반적으로 은식 변환을 피하는 것이 가장 좋지만.그러나 float도 다른 상하문에서 사용할 수 있고 정수도 사용할 수 있다.
    이를 위해 IntWithPower을 정의해야 합니다.
    class IntWithPower {
    public:
      IntWithPower(int num) : m_num(num) {}
      int power(IntWithPower exp) {
        return pow(m_num, exp);
      }
      operator int() const {return m_num;}
    private:
      int m_num;
    }
    
    만약 우리가 지금 예시를 검사한다면, 우리는 operator intprintPower(IntWithPower{5}, IntWithPower{4});이 모두 번역되는 것을 보게 될 것이다. 그러나 printPower(IntWithPower{5}, 4L);은 정수가 아니기 때문에 실패할 것이다.
    네, 방금 말씀드린 바와 같이 printPower(IntWithPower{5}, 3.0); 은 부동점수를 연산하지만 우리는 포인트만 받아들입니다.그에 상응하여 우리의 개념을 갱신합시다!
    template <typename Base, typename Exponent>
    concept HasPower = (std::integral<Exponent> || std::floating_point<Exponent>) && requires (Base base, Exponent exponent) { 
        base.power(exponent);
    };
    
    현재 우리는 3.0을 호출할 수 있으며 어떤 유형의 powprintPower의 개념을 만족시키고 정수와 부동점수를 지수로 한다.
    이제 완벽한 예를 살펴보겠습니다.
    #include <cmath>
    #include <iostream>
    #include <string>
    #include <concepts>
    #include <type_traits>
    
    template <typename Base, typename Exponent>
    concept HasPower = (std::integral<Exponent> || std::floating_point<Exponent>) && requires (Base base, Exponent exponent) { 
        base.power(exponent);
    };
    
    class IntWithPower {
    public:
      IntWithPower(int num) : m_num(num) {}
      int power(IntWithPower exp) {
        return pow(m_num, exp);
      }
      operator int() const {return m_num;}
    private:
      int m_num;
    };
    
    template<>
    struct std::is_integral<IntWithPower> : public std::integral_constant<bool, true> {};
    
    template<typename Exponent> 
    void printPower(HasPower<Exponent> auto number, Exponent exponent) {
      std::cout << number.power(exponent) << '\n';
    }
    
    
    int main() {
      printPower(IntWithPower{5}, IntWithPower{4});
      printPower(IntWithPower{5}, 4L);
      printPower(IntWithPower{5}, 3.0);
    }
    
    이 예에서 우리는 하나의 개념을 어떻게 작성하는지 관찰할 수 있다. 이 개념은 어떤 함수의 존재를 기대하고 이 함수는 서로 다른 구속 유형의 매개 변수를 받아들일 수 있다.우리는 또한 어떻게 한 유형이 내재적인 유형 특징을 만족시킬 수 있는지, 예를 들어 base을 볼 수 있다.

    결론
    오늘 우리는 자신의 개념을 어떻게 쓰는지 발견하기 시작했다.우선, 우리는 이미 존재하는 개념을 더욱 복잡한 개념으로 조합한 다음에 제약을 받는 유형의 조작의 유효성에 대해 계속 요구를 한 다음에 매개 변수 목록을 가지고 있거나 가지고 있지 않은 함수 호출을 통해 작성 요구를 완성한다.
    다음에 우리는 계속해서 유형을 되돌려주고 유형을 생성한 다음에 수요를 끼워 넣을 것이다.
    기대해주세요!
    C++ 개념에 대한 자세한 정보를 알고 싶으면 check out my book on Leanpub을 클릭하세요!

    더욱 깊이 연락하다
    이 글이 재미있다면 subscribe to my personal blog을 눌러서 계속 연결합시다!

    좋은 웹페이지 즐겨찾기