유형 피쳐는 어떻게 사용합니까?

32573 단어 templatestypetraitscpp
이 프로젝트의 파생 제품으로서 저는 유형 특징의 세계를 깊이 연구했고 지난주에 우리는 유형 특징이 무엇인지, 그리고 그것이 어떻게 실현되는지 토론하기 시작했습니다.
나는 내 글을 5분에서 10분 사이의 독서 시간에 놓는 것을 더 좋아하기 때문에 여기까지 하기로 결정했다.유형 특징에 대한 기본적인 이해에 따라 이제는 그것을 어떻게 사용하는지 볼 때가 되었다.우리는 그들이 어떻게 서로 다른 템플릿을 컴파일하기 위해 조건을 전문적으로 설정하고 유형을 변경하는지 볼 것이다.

조건 컴파일
앞서 언급한 바와 같이 우리는 유형 특징을 사용하여 특정 유형의 특징을 바탕으로 템플릿을 사용하는 것을 금지할 수 있다.강조해야 할 것은 실행 시 비용이 없고 모든 검사 (오류) 가 컴파일할 때 발생한다는 것이다.
우리 기본적인 예를 하나 봅시다.
만약 우리가 addSigned(T a, T b) 라는 함수를 작성하려고 한다면, 그 중 기호가 없는 수만 추가하기 때문에, 그 결과는 입력보다 크다고 확신합니다. (넘침 오류를 무시합니다.)
만약 우리가 간단한 템플릿을 작성한다면, 문제는 우리가 여전히 무기호 숫자로 그것을 호출할 수 있다는 것이다.
#include <iostream>
#include <type_traits>

template <typename T>
T addUnsigned(T a, T b) {
    return a + b;
}


int main() {
    int a = 5;
    int b = -6;
    auto s = addUnsigned(a, b);
    if (s < a || s < b) {
        std::cout << "Oh, oh! The sum is smaller than one of the inputs!\n";
    } else {
        std::cout << "OK! The sum is larger than any of the inputs!s\n";
    }
}
/*
Oh, oh! The sum is smaller than one of the inputs!
*/
유형 특징은 서로 다른 방식으로 우리가 이 문제를 해결하는 것을 도울 수 있다.
static_assert우리는 간단하게 정적 단언T이 무기호 유형이라고 할 수 있다.
template <typename T>
T addUnsigned(T a, T b) {
    static_assert(std::is_unsigned<T>::value, "T must be unsigned!" );
    return a + b;
}
주의해야 할 것은 브리 상하문에서 사용할 때 우리는 std::is_unsigned<T>을 간단하게 사용할 수 없다. 왜냐하면 브리 유형이 아닌 브리 유형이기 때문이다std::integral_constant. 그러나 우리는 브리 유형value의 정적 구성원 상량bool이 필요하다.C++17로 인해 우리는 직접 사용할 수 있다std::is_unsigned_v<T>.
따라서 static_assert 컴파일할 때 브리 값을 첫 번째 매개 변수로 하고 오류 메시지를 두 번째 매개 변수로 한다.
그리고 만약 우리가 그것을 다른 종류와 함께 사용한다면, 우리는 컴파일러로부터 좋은 오류 메시지를 얻을 것이다.
main.cpp: In instantiation of 'T addUnsigned(T, T) [with T = int]':
main.cpp:14:30:   required from here
main.cpp:6:40: error: static assertion failed: T must be unsigned, but it's
    6 |     static_assert(std::is_unsigned<T>::value, "T must be unsigned, but it's");
      |                     
잘못된 정보가 충분하지 않다고 생각한다면, 더 좋은 정보를 작성하십시오. 왜냐하면 그것은 당신의 것입니다. static_assert std::enable_if현재 우리는 서로 다른 추가를 지원하고 싶다고 가정하고 같은 함수 서명T add(T a, T b)을 사용하려고 한다.우리는 std::enable_if 헤드의 <type_traits> 원 함수를 사용할 수 있다.
#include <iostream>
#include <type_traits>

template <typename T, typename std::enable_if<std::is_unsigned<T>::value, T>::type* = nullptr>
T add(T a, T b) {
    std::cout << "add called with unsigned numbers\n";
    return a + b;
}

template <typename T, typename std::enable_if<std::is_signed<T>::value, T>::type* = nullptr>
T add(T a, T b) {
    std::cout << "add called with signed numbers\n";
    return a + b;
}

int main() {
    add(5U, 6U);
    add(5, 6);
    add(5, -6);
    // add(5U, -6); // error: no matching function for call to 'add(unsigned int, int)'
}
/*
add called with unsigned numbers
add called with signed numbers
add called with signed numbers
*/
템플릿 매개 변수 목록만 다른 두 함수를 같은 서명으로 정의할 수 있음을 알 수 있습니다.여기서 우리는 enable_if를 사용하여 is_signed 또는 is_unsigned 특징이 진실로 평가되면 한 함수 또는 다른 함수를 호출해야 한다는 것을 나타낸다.
만약 std::enable_if 수신true을 첫 번째 매개 변수로 한다면 두 번째 매개 변수에서 얻은 내부type가 있을 것이다.만약 첫 번째 파라미터의 계산 결과가 false라면 내부 파라미터type가 없어서 교체에 실패합니다.최종적으로 컴파일 오류가 발생하지 않도록 하기 위해서, 우리는 이 유형들을 기본적으로 nullptr 로 설정합니다.
나는 이것이 여전히 약간 모호하다는 것을 알고 있지만, 이 부분은 통상적으로 SFINAE라고 불리며, 자신의 글을 발표할 가치가 있다.우리는 앞으로 몇 주 동안 이 점을 상세하게 소개할 것이다.
if constexprC++17 이래로 세 번째 방법이 있는데, 마치 우리가 가지고 있는 if constexpr 와 같다.if constepxr를 사용하면 우리는 컴파일할 때 조건을 계산할 수 있고 컴파일에서 지점을 버릴 수 있다.if constexpr를 사용하면 난해한 원 프로그래밍 구조를 현저하게 간소화할 수 있다.
이를 사용하여 이전 예제를 줄이는 방법을 살펴보겠습니다.
#include <iostream>
#include <type_traits>

template <typename T>
T add(T a, T b) {
    if constexpr (std::is_signed<T>::value) {
        std::cout << "add called with signed numbers\n";
        return a + b;
    }
    if constexpr (std::is_unsigned<T>::value) {
        std::cout << "add called with unsigned numbers\n";
        return a + b;
    }
    static_assert(std::is_signed<T>::value || std::is_unsigned<T>::value, "T must be either signed or unsingned!");
}


int main() {
    add(5U, 6U);
    add(5, 6);
    add(5, -6);
    // add(5U, -6); // error: no matching function for call to 'add(unsigned int, int)'
    // add("a", "b"); // error: static assertion failed: T must be either signed or unsingned!
}
/*
add called with unsigned numbers
add called with signed numbers
add called with signed numbers
*/
if constexpr를 사용하면 우리는 컴파일할 때 조건을 평가할 수 있기 때문에 유형 특징에 따라 컴파일할 때 결정을 할 수 있다.나는 내가 유일하게 그것이 enable_if보다 훨씬 간단하다고 생각하는 사람이 아니라고 믿는다
저희가 간소화해도 될까요?네, 앞의 모든 예는 이렇습니다.C++17에 제가 언급한 단축키가 있기 때문에, 형식 trait의 value 에 접근할 필요가 없습니다. 따라서 일부 원 함수는 직접 값을 되돌릴 수 있습니다.이러한 피쳐는 해당 유형의 피쳐와 같은 방식으로 불리지만 다음과 같이 추가됩니다_v.
#include <iostream>
#include <type_traits>

template <typename T>
T add(T a, T b) {
    if constexpr (std::is_signed_v<T>) {
        std::cout << "add called with signed numbers\n";
        return a + b;
    }
    if constexpr (std::is_unsigned_v<T>) {
        std::cout << "add called with unsigned numbers\n";
        return a + b;
    }
    static_assert(std::is_signed_v<T> || std::is_unsigned_v<T>, "T must be either signed or unsingned!");
}


int main() {
    add(5U, 6U);
    add(5, 6);
    add(5, -6);
    // add(5U, -6); // error: no matching function for call to 'add(unsigned int, int)'
    // add("a", "b"); // error: static assertion failed: T must be either signed or unsingned!
}
/*
add called with unsigned numbers
add called with signed numbers
add called with signed numbers
*/

유형 변경
이제 유형 특징이 어떻게 유형을 바꾸는지 살펴보자.<type_traits> 제목에 제공된 템플릿은
  • 지정된 유형
  • 에서 추가/제거const 및/또는volatile 설명자
  • 주어진 유형의 인용이나 포인터를 추가하거나 삭제
  • 기호가 있거나 없는 유형 생성
  • 수조
  • 에서 차원 삭제
  • 등(우리가 이미 간략하게 본 enable if 포함)
  • 우리는 세 가지 예를 보았다.

    추가/제거const 설명자std::add_const/std::remove_const를 사용하여 유형의 최상위 상수를 추가/제거할 수 있습니다.
    #include <iostream>
    #include <type_traits>
    
    int main() {
        using Integer = int;
    
        std::cout << "Integer is " << (std::is_same<int, Integer>::value
            ? "int" : "not an int") << '\n';
        std::cout << "The result of std::add_const<Integer> is " << (std::is_same<const int, std::add_const<Integer>::type>::value
            ? "const int" : "not const int") << '\n';
        std::cout << "The result of std::add_const<Integer> is " << (std::is_same<int, std::add_const<Integer>::type>::value
            ? "a simple int" : "not a simple int") << '\n';        
    
        using ConstInteger = const int;
    
        std::cout << "ConstInteger is " << (std::is_same<const int, ConstInteger>::value
            ? "const int" : "not a const int") << '\n';
        std::cout << "The result of std::remove_const<ConstInteger> is " << (std::is_same<int, std::remove_const<ConstInteger>::type>::value
            ? "int" : "not an int") << '\n';
    }
    /*
    Integer is int
    The result of std::add_const<Integer> is const int
    The result of std::add_const<Integer> is not a simple int
    ConstInteger is const int
    The result of std::remove_const<ConstInteger> is int
    */
    
    비교할 때 방문type 중첩 구성원을 확인하십시오.C++17 때문에 std::add_const_t 대신 std::add_const<T>::type 형식을 직접 가져와서 내용을 더 짧고 읽을 수 있습니다.
    그런데 이게 어떻게 쓸모가 있죠?위의 예는 이미 답을 제시했다.두 가지 유형을 비교하려면 그것들의 한정부호를 고려하지 않고 먼저 const한정부호를 삭제한 다음에 std::is_same와 비교할 수 있다.전화를 하지 않으면 std::remove_constT의 차이를 비교할 수 있지만 전화를 하면 const TT를 비교할 수 있다.
    같은 논리로 인용이나 바늘을 삭제하는 용례를 찾을 수 있습니다.

    기호 없음을 기호 있음으로 변환
    유형 피쳐를 사용하여 기호 유형을 비기호 유형으로 변환하거나 반대로 변환할 수 있습니다.
    #include <iostream>
    #include <type_traits>
    
    int main() {
    
        std::cout << "Making signed to unsigned " << (std::is_same<unsigned int, std::make_unsigned_t<int>>::value
            ? "worked" : "did not work") << '\n';
        std::cout << "Making unsigned to signed " << (std::is_same<int, std::make_signed_t<unsigned int>>::value
            ? "worked" : "did not work") << '\n';
    }
    /*
    Making signed to unsigned worked
    Making unsigned to signed worked
    */
    
    보시다시피, 저희는 T 스타일의 조수 함수를 사용하여 수정된 형식을 직접 되돌려줍니다.
    _t 컴파일할 때 두 가지 유형 선택std::conditional를 사용하면 컴파일할 때 조건에 따라 두 가지 유형을 선택할 수 있습니다.너는 그것을 번역할 때의 삼원 연산자로 상상할 수 있다. 비록 그것이 더 읽기 어려울 수도 있지만.
    #include <iostream>
    #include <type_traits>
    #include <typeinfo>
    
    int main() 
    {
        typedef std::conditional<true, int, double>::type Type1;
        typedef std::conditional<false, int, double>::type Type2;
        typedef std::conditional<sizeof(int) >= sizeof(double), int, double>::type Type3;
    
        std::cout << typeid(Type1).name() << '\n';
        std::cout << typeid(Type2).name() << '\n';
        std::cout << typeid(Type3).name() << '\n';
    }
    /*
    i
    d
    d
    */
    
    들어오는 형식의 크기를 조건으로 하는 예시를 찾을 수 있습니다.어떤 경우, 메모리 레이아웃에 더 잘 어울리는 채우기를 원할 수도 있습니다.어떻게 사이즈에 따라 결정합니까?간단합니다std::conditional 연산자:
    #include <iostream>
    #include <type_traits>
    #include <typeinfo>
    
    class SmallSize{};
    class BigSize{};
    
    template <class T>
    using ContainerType =
    typename std::conditional<sizeof(T) == 1, SmallSize, BigSize>::type;
    
    int main()
    {
        ContainerType<bool> b;
        std::cout << typeid(b).name() << '\n';
    
        ContainerType<int> i;
        std::cout << typeid(i).name() << '\n';
    }
    /*
    9SmallSize
    7BigSize
    */
    

    결론
    오늘 우리는 유형 특징을 어떻게 사용하여 조건 컴파일을 하는지, 그리고 그것들을 어떻게 사용해서 유형을 바꾸는지 이해했다.우리도 SFINAE에 대해 언급했는데 이것은 몇 주 후의 화제가 될 것이다.
    기대해주세요!

    더욱 깊이 연락하다
    만약 당신이 이 문장을 좋아한다면,
  • like버튼을 클릭하면,

  • subscribe to my newsletter
  • 계속 연결합시다!
  • 좋은 웹페이지 즐겨찾기