Template-metaprograming 또는 constexpr, C++17 입문서 및 비교, 2부

Mar 11 2023
C++에서 SFINAE(대체 실패는 오류가 아님) 및 constexpr은 각각 템플릿 메타프로그래밍 및 컴파일 타임 평가에서 중요한 역할을 하는 두 가지 중요한 개념입니다. 둘 다 더 강력하고 효율적인 코드를 생성할 수 있지만 사용 사례와 제한 사항이 다릅니다.

C++에서 SFINAE(대체 실패는 오류가 아님) 및 constexpr은 각각 템플릿 메타프로그래밍 및 컴파일 타임 평가에서 중요한 역할을 하는 두 가지 중요한 개념입니다. 둘 다 더 강력하고 효율적인 코드를 생성할 수 있지만 사용 사례와 제한 사항이 다릅니다. 이 기사에서는 이러한 개념을 심층적으로 설명하고 코드 예제를 제공하며 장단점을 비교 및 ​​대조합니다.

SFINAE

SFINAE는 컴파일러가 특정 식을 평가할 수 있는지 여부에 따라 오버로드 집합에서 함수를 제외할 수 있도록 하는 메커니즘입니다. 예를 들어 SFINAE를 사용하여 정수 유형에 대해서만 숫자의 계승을 계산하는 템플릿 함수를 만들 수 있습니다.

이 코드에서는 가 정수 유형인지 std::enable_if여부에 따라 함수 템플릿을 조건부로 활성화 또는 비활성화하는 데 사용하고 있습니다. 정수가 아니면 T대체 실패가 발생하고 컴파일러는 오버로드 집합에서 이 함수를 제거합니다. 이렇게 하면 함수가 인스턴스화되지 않으므로 컴파일 중에 발생하는 오류를 방지할 수 있습니다.Tstd::enable_if

또한 SFINAE를 사용하여 여러 조건에 의존하는 더 복잡한 템플릿 함수를 만들 수 있습니다. 예를 들어 하나 이상의 특정 멤버 함수가 있는 유형만 허용하는 템플릿 함수를 만들 수 있습니다. 이 작업을 수행하기 전에 std::declval 유틸리티를 이해해야 합니다 . 기본 생성자 없이 특정 유형의 임시 개체를 만들 수 있습니다. 컴파일러 탐색기 에 붙여넣은 cppreference.com 의 예 :

위와 같이 구조체에 NonDefault기본 소멸자가 삭제되었음을 알 수 있습니다! 메서드를 실행할 때 후자와 struct Defaultreturn의 타입이 같은지 비교하고자 합니다 foo. Default암시적 기본 소멸자가 있으므로 임시를 만들고 Default호출합니다 foo. 유형을 공제하기 위해 decltype 에 전달 하고 해당 유형(int)의 변수 n1을 1로 선언합니다. 이제 fooNonDefault 구조체에서 동일한 유형을 반환하지만 기본 생성자가 없기 때문에 확인하려고 합니다. 위에서 논의한 std::declval 에 의지하여 여기에서 rvalue 참조를 반환 합니다.NonDefault임시 개체에서 foo를 호출할 수 있도록 합니다. 그런 다음 std::decltype을 사용하여 동일한 작업을 수행 하고 변수를 n1새로 선언된 변수에 할당합니다 n2. std::cout두 변수 모두에 대해 값 1을 인쇄합니다 .

이제 std::enable_if를begin() 사용하여 2개의 메서드가 호출 되었는지 확인하는 예제 자체로 이동해 보겠습니다 end().

이 코드에서 우리는 기본 생성자 없이 std::declval유형의 임시 객체를 생성할 수 있도록 하는 것을 사용하고 T있으며 멤버 함수 decltype의 유형을 결정하는 데 사용합니다 . declval 을 사용하려면 decltype 을 사용해야 합니다 . 그렇지 않으면 허용되지 않습니다...begin()end()

std::is_same그런 다음 이러한 유형이 동일한지 확인하는 데 사용합니다 . 그렇다면 함수 템플릿이 활성화되고, 그렇지 않으면 오버로드 세트에서 제외됩니다.

SFINAE의 한 가지 제한은 템플릿 인수 추론 중에만 작동한다는 것입니다. 즉, 오버로드 집합이 이미 생성된 후에는 조건부로 함수를 제외하는 데 사용할 수 없습니다. 여기가 if constexpr들어오는 곳입니다.

if constexpr

if constexpr컴파일 타임 상수 표현식을 기반으로 컴파일 타임에 조건부로 코드를 실행할 수 있게 해주는 C++17에 도입된 새로운 기능입니다. SFINAE와 달리 if constexpr오버로드 세트가 생성된 후에도 코드가 컴파일되지 않도록 제외하는 데 사용할 수 있습니다.

if constexpr예를 들어 숫자의 계승을 계산하는 템플릿 함수를 만드는 데 사용할 수 있지만 정수 유형에 대해서만 사용할 수 있습니다.

이 코드에서는 가 정수 유형인지 if constexpr여부에 따라 조건부로 코드를 실행하는 데 사용하고 있습니다. T가 적분 이면 T함수는 계승 계산을 실행합니다. 그렇지 않으면 정적 어설션이 실패하여 코드가 컴파일되지 않습니다. std::is_integral_v의 줄임말 입니다 std::is_integral<T>::value.

if constexpr또한 여러 조건에 의존하는 더 복잡한 템플릿 함수를 만드는 데 사용할 수도 있습니다 . 예를 들어 위에서 std::enable_if 로 했던 것처럼 a begin()와 멤버 함수 가 모두 있는 유형만 허용하는 템플릿 함수를 만들 수 있습니다 .end()

has_begin_end위의 코드 샘플에서 유형에 멤버 함수가 모두 있는지 확인하는 유형 T특성 을 작성하고 있습니다 . 우리는 유형을 참조 유형으로 변환하는 데 사용합니다. 이를 통해 앞에서 설명한 것처럼 개체를 구성하지 않고도 식 내에서 멤버 함수를 호출할 수 있습니다 . 먼저 on 멤버 함수를 호출 하고 성공하면 쉼표 연산자를 사용하여 결과를 버립니다. 다음으로 on 을 호출하려고 합니다 . 성공하면 using 반환 유형을 얻습니다 . inside 를 사용하여 참조 유형에 대한 멤버 함수만 호출할 수 있으므로 필수 입니다 . 마지막으로 이 모든 것이 성공하면 반환하는 데 사용합니다.beginendstd::declvalTdecltypebegin()std::declval<T>()end()std::declval<T>()decltypedecltypedeclvaldecltypevoid_tvoid, 그렇지 않으면 의 템플릿 매개변수는 void_t평가에 실패하고 이름 조회 중에 특수화를 확인할 수 없습니다.

S FINAE 및 constexpr 의 사용법을 대조하기 위해 사용할 수 있는 유비쿼터스 예는 계승 알고리즘을 구현하는 것입니다.

SFINAE의 예

위의 코드에서 우리는 SFINAE를 사용하여 함수의 오버로드 세트에서 정수가 아닌 유형을 제외하고 숫자인지 확인하기 위해 함께 factorial사용합니다 .std::enable_ifstd::is_integral

if constexpr 의 예

가 정수 유형 if constexpr인 경우 팩토리얼 계산 코드를 조건부로 실행하기 위해 사용하고 있습니다 . T정수 유형이 아닌 경우 T정적 어설션으로 인해 함수가 실패하여 코드가 컴파일되지 않습니다.

사용 사례 및 권장 사항

SFINAE 및 if constexpr은 둘 다 보다 효율적이고 표현력이 풍부한 코드를 만드는 데 사용할 수 있는 강력한 도구입니다. 그러나 사용 사례와 제한 사항이 다릅니다.

SFINAE는 함수 인수의 속성에 따라 설정된 오버로드에서 함수를 조건부로 제외하려는 경우에 가장 적합합니다. 예를 들어 SFINAE를 사용하여 특정 유형만 허용하거나 특정 속성이 있는 함수 템플릿을 만들 수 있습니다. SFINAE는 여러 조건에 의존하는 더 복잡한 템플릿 함수를 생성하려는 경우에도 유용합니다.

if constexpr반면에 컴파일 타임 상수 표현식을 기반으로 컴파일 타임에 조건부로 코드를 실행하려는 경우에 가장 적합합니다. if constexpr조건이 템플릿 매개 변수 전체에 분산되는 대신 함수 본문의 일부이기 때문에 보다 간결하고 읽기 쉬운 코드를 생성하려는 경우에 특히 유용합니다. if constexpr정적 어설션을 사용하여 보다 자세한 정보를 제공할 수 있으므로 보다 유익한 오류 메시지를 제공하려는 경우에도 유용합니다.

SFINAE를 사용할지 또는 constexpr 인 경우를 결정할 때 다음을 고려하십시오.

  • 함수 인수의 속성을 기반으로 함수를 조건부로 제외하려는 경우 SFINAE를 사용합니다.
  • 컴파일 타임 상수 표현식을 기반으로 코드를 조건부로 실행하려는 경우 if constexpr을 사용합니다 .
  • 여러 조건에 따라 달라지는 보다 복잡한 템플릿 함수를 생성하려는 경우 SFINAE를 사용합니다.
  • 더 간결하고 읽기 쉬운 코드를 만들거나 더 많은 정보를 제공하는 오류 메시지를 제공하려는 경우 if constexpr을 사용합니다 .
  • SFINAE가 템플릿 인수 추론 중에만 작동하거나 constexpr에 C++17 지원이 필요한 경우 와 같은 각 접근 방식의 제한 사항을 고려하십시오 .

std::enable_if 및 if constexpr 사용을 통한 SFINAE는 효율적이고 표현적인 코드를 생성할 수 있는 C++의 두 가지 중요한 개념입니다. SFINAE는 함수 인수의 속성을 기반으로 함수를 조건부로 제외하려는 경우에 가장 적합하며 if constexpr은 컴파일 타임 상수 표현식을 기반으로 조건부로 코드를 실행하려는 경우에 가장 적합합니다. 유형에 기본 생성자가 없을 때 특정 멤버 함수의 존재를 확인하기 위해 delctype 과 함께 declval을 사용할 수 있다는 것을 배웠습니다 . SFINAE와 if constexpr 의 차이점을 이해함으로써템플릿 함수를 작성하고 보다 효율적이고 표현력이 풍부한 코드를 생성할 때 더 나은 결정을 내릴 수 있습니다.

참고: 파트 1의 경우 확인https://medium.com/@joao_vaz/template-meta-programming-in-c-17-primer-1b493a22d51

<a href=”의 사진https://www.freepik.com/free-vector/business-decisions-concept-illustration_11392273.htm#query=choice&position=26&from_view=search&track=sphFreepik의 ">storyset 이미지</a>