Namespaces
Variants

std:: enable_if

From cppreference.net
Metaprogramming library
Type traits
Type categories
(C++11)
(C++11) ( DR* )
Type properties
(C++11)
(C++11)
(C++14)
(C++11) (deprecated in C++26)
(C++11) ( until C++20* )
(C++11) (deprecated in C++20)
(C++11)
Type trait constants
Metafunctions
(C++17)
Supported operations
Relationships and property queries
Type modifications
Type transformations
(C++11) (deprecated in C++23)
(C++11) (deprecated in C++23)
(C++11)
(C++11) ( until C++20* ) (C++17)

enable_if
(C++11)
(C++17)
Compile-time rational arithmetic
Compile-time integer sequences
헤더에 정의됨 <type_traits>
template < bool B, class T = void >
struct enable_if ;
(C++11부터)

만약 B true 라면, std::enable_if T 와 동일한 type 공용 멤버 typedef를 가집니다; 그렇지 않으면, 멤버 typedef가 존재하지 않습니다.

이 메타함수는 C++20의 concepts 이전에 SFINAE 를 활용하는 편리한 방법으로, 특히 타입 특성에 기반하여 candidate set 에서 함수를 조건부로 제거하여 서로 다른 타입 특성에 기반한 별도의 함수 오버로드나 특수화를 가능하게 합니다.

std::enable_if 는 다음과 같은 다양한 형태로 사용할 수 있습니다:

  • 추가 함수 인수로 (대부분의 연산자 오버로드에는 적용되지 않음),
  • 반환 타입으로 (생성자와 소멸자에는 적용되지 않음),
  • 클래스 템플릿 또는 함수 템플릿 매개변수로.

프로그램이 std::enable_if 에 대한 특수화를 추가하는 경우, 동작은 정의되지 않습니다.

목차

멤버 타입

유형 정의
type B 의 값에 따라 T 이거나 해당 멤버가 없음

헬퍼 타입

template < bool B, class T = void >
using enable_if_t = typename enable_if < B,T > :: type ;
(C++14부터)

가능한 구현

template<bool B, class T = void>
struct enable_if {};
template<class T>
struct enable_if<true, T> { typedef T type; };

참고 사항

흔히 하는 실수는 기본 템플릿 인자만 다른 두 함수 템플릿을 선언하는 것입니다. 이는 동일한 함수 템플릿의 재선언으로 처리되기 때문에 작동하지 않습니다(기본 템플릿 인자는 함수 템플릿 동등성 에서 고려되지 않습니다).

/* 잘못된 예시 */
struct T
{
    enum { int_t, float_t } type;
    template<typename Integer,
             typename = std::enable_if_t<std::is_integral<Integer>::value>>
    T(Integer) : type(int_t) {}
    template<typename Floating,
             typename = std::enable_if_t<std::is_floating_point<Floating>::value>>
    T(Floating) : type(float_t) {} // 오류: 재정의로 처리됨
};
/* 올바른 예시 */
struct T
{
    enum { int_t, float_t } type;
    template<typename Integer,
             std::enable_if_t<std::is_integral<Integer>::value, bool> = true>
    T(Integer) : type(int_t) {}
    template<typename Floating,
             std::enable_if_t<std::is_floating_point<Floating>::value, bool> = true>
    T(Floating) : type(float_t) {} // 정상
};

네임스페이스 범위 함수 템플릿의 상수 템플릿 매개변수 타입에서 enable_if 를 사용할 때는 주의해야 합니다. Itanium ABI와 같은 일부 ABI 사양은 맹글링(mangling)에 상수 템플릿 매개변수의 인스턴스화 종속 부분을 포함하지 않아, 두 개의 서로 다른 함수 템플릿 특수화가 동일한 맹글링 이름을 갖게 되어 오류로 함께 링크될 수 있습니다. 예를 들어:

// 첫 번째 번역 단위
struct X
{
    enum { value1 = true, value2 = true };
};
template<class T, std::enable_if_t<T::value1, int> = 0>
void func() {} // #1
template void func<X>(); // #2
// 두 번째 번역 단위
struct X
{
    enum { value1 = true, value2 = true };
};
template<class T, std::enable_if_t<T::value2, int> = 0>
void func() {} // #3
template void func<X>(); // #4

함수 템플릿 #1과 #3은 서로 다른 시그니처를 가지며 별개의 템플릿입니다. 그럼에도 불구하고, #2와 #4는 서로 다른 함수 템플릿의 인스턴스화임에도 불구하고 Itanium C++ ABI에서 맹글링된 이름 이 동일합니다( _Z4funcI1XLi0EEvv ). 이는 링커가 이들을 오류로 동일한 엔티티로 간주하게 됨을 의미합니다.

예제

#include <iostream>
#include <new>
#include <string>
#include <type_traits>
namespace detail
{ 
    void* voidify(const volatile void* ptr) noexcept { return const_cast<void*>(ptr); } 
}
// #1, 반환 타입을 통해 활성화됨
template<class T>
typename std::enable_if<std::is_trivially_default_constructible
**번역 결과:**
std::is_trivially_default_constructible
**번역 설명:**
- HTML 태그와 속성은 그대로 유지
- C++ 관련 용어(`std::is_trivially_default_constructible`)는 번역하지 않음
- 링크 구조와 클래스 속성 완전히 보존
- 전문적인 기술 문서 번역 기준 준수<T>::value>::type 
    construct(T*) 
{
    std::cout << "trivially default constructible T의 기본 생성\n";
}
// 위와 동일
template<class T>
typename std::enable_if<!std::is_trivially_default_constructible<T>::value>::type 
    construct(T* p) 
{
    std::cout << "비트리비얼리 기본 생성 가능하지 않은 T의 기본 생성\n";
    ::new(detail::voidify(p)) T;
}
// #2
template<class T, class... Args>
std::enable_if_t<std::is_constructible<T, Args&&...>::value> // 헬퍼 타입 사용
    construct(T* p, Args&&... args) 
{
    std::cout << "T를 operation으로 생성 중\n";
    ::new(detail::voidify(p)) T(static_cast<Args&&>(args)...);
}
// #3, 매개변수를 통해 활성화됨
template<class T>
void destroy(
    T*, 
    typename std::enable_if<
        std::is_trivially_destructible<T>::value
    >::type* = 0)
{
    std::cout << "trivially destructible T를 파괴하는 중\n";
}
// #4, 상수 템플릿 매개변수를 통해 활성화됨
template<class T,
         typename std::enable_if<
             !std::is_trivially_destructible<T>{} &&
             (std::is_class<T>{} || std::is_union<T>{}),
             bool>::type = true>
void destroy(T* t)
{
    std::cout << "비-자명 소멸자를 가진 T를 파괴하는 중\n";
    t->~T();
}
// #5, 타입 템플릿 매개변수를 통해 활성화됨
template<class T,
	 typename = std::enable_if_t<std::is_array<T>::value>>
void destroy(T* t) // 참고: 함수 시그니처는 수정되지 않음
{
    for (std::size_t i = 0; i < std::extent<T>::value; ++i)
        destroy((*t)[i]);
}
/*
template<class T,
	 typename = std::enable_if_t<std::is_void<T>::value>>
void destroy(T* t) {} // 오류: #5와 동일한 시그니처를 가짐
*/
// A의 부분 특수화는 템플릿 매개변수를 통해 활성화됩니다
template<class T, class Enable = void>
class A {}; // primary template
template<class T>
class A<T, typename std::enable_if<std::is_floating_point<T>::value>::type>
{}; // 부동 소수점 타입 전문화
int main()
{
    union { int i; char s[sizeof(std::string)]; } u;
    construct(reinterpret_cast<int*>(&u));
    destroy(reinterpret_cast<int*>(&u));
    construct(reinterpret_cast<std::string*>(&u), "안녕하세요");
    destroy(reinterpret_cast<std::string*>(&u));
    A<int>{}; // OK: 기본 템플릿과 일치함
    A<double>{}; // OK: 부분 특수화와 일치함
}

출력:

default constructing trivially default constructible T
destroying trivially destructible T
constructing T with operation
destroying non-trivially destructible T

참고 항목

(C++17)
void 가변 인자 앨리어스 템플릿
(앨리어스 템플릿)