types_enable_if
types_enable_if (enable_if 타입)
이 페이지는 C++ 표준 라이브러리의 std::enable_if 메타함수에 대해 설명해요. std::enable_if는 컴파일 타임에 불리언 조건에 따라 타입을 선택하거나, SFINAE를 활용해 함수 오버로드 후보에서 조건부로 함수를 제외하는 데 사용돼요. C++20의 concepts가 도입되기 전에 널리 쓰인 중요한 기법이에요.
출처: cppreference
본문
<type_traits> 헤더에 정의되어 있어요.
| 정의 | |
|---|---|
template < bool B , class T = void > struct enable_if ; |
(C++11부터) |
만약 B가 true라면, std::enable_if는 T와 같은 공용 멤버 typedef type을 가져요. 그렇지 않으면 type 멤버가 존재하지 않아요.
이 메타함수는 C++20의 concepts 이전에 SFINAE를 활용하는 편리한 방법이에요. 특히 타입 특성(type traits)에 기반하여 함수를 후보 집합에서 조건부로 제거하고, 서로 다른 타입 특성에 따라 별도의 함수 오버로드나 특수화를 허용하는 데 사용돼요.
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 ; }; |
|---|
참고 사항
흔한 실수는 기본 템플릿 인자만 다른 두 함수 템플릿을 선언하는 거예요. 이는 동작하지 않아요. 왜냐하면 그 선언들은 같은 함수 템플릿의 재선언으로 처리되기 때문이에요 (기본 템플릿 인자는 함수 템플릿 동등성에 포함되지 않아요).
/* WRONG */
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) {} // error: treated as redefinition
};
/* RIGHT */
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) {} // OK
};
네임스페이스 범위 함수 템플릿의 비타입 템플릿 매개변수 타입에 enable_if를 사용할 때는 주의해야 해요. Itanium ABI와 같은 일부 ABI 명세는 비타입 템플릿 매개변수의 인스턴스화 의존 부분을 name mangling에 포함하지 않아요. 즉, 서로 다른 두 함수 템플릿의 특수화가 같은 mangled name을 가질 수 있고, 잘못 링크될 수 있어요. 예를 들어:
// first translation unit
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
// second translation unit
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에서 같은 mangled name (_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, enabled via the return type
template<class T>
typename std::enable_if<std::is_trivially_default_constructible<T>::value>::type
construct(T*)
{
std::cout << "default constructing trivially default constructible T\n";
}
// same as above
template<class T>
typename std::enable_if<!std::is_trivially_default_constructible<T>::value>::type
construct(T* p)
{
std::cout << "default constructing non-trivially default constructible T\n";
::new(detail::voidify(p)) T;
}
// #2
template<class T, class... Args>
std::enable_if_t<std::is_constructible<T, Args&&...>::value> // Using helper type
construct(T* p, Args&&... args)
{
std::cout << "constructing T with operation\n";
::new(detail::voidify(p)) T(static_cast<Args&&>(args)...);
}
// #3, enabled via a parameter
template<class T>
void destroy(
T*,
typename std::enable_if<
std::is_trivially_destructible<T>::value
>::type* = 0)
{
std::cout << "destroying trivially destructible T\n";
}
// #4, enabled via a non-type template parameter
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 << "destroying non-trivially destructible T\n";
t->~T();
}
// #5, enabled via a type template parameter
template<class T,
typename = std::enable_if_t<std::is_array<T>::value>>
void destroy(T* t) // note: function signature is unmodified
{
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) {} // error: has the same signature with #5
*/
// the partial specialization of A is enabled via a template parameter
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>
{}; // specialization for floating point types
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), "Hello");
destroy(reinterpret_cast<std::string*>(&u));
A<int>{}; // OK: matches the primary template
A<double>{}; // OK: matches the partial specialization
}
출력:
default constructing trivially default constructible T
destroying trivially destructible T
constructing T with operation
destroying non-trivially destructible T
같이 보기
void_t (C++17) |
void 가변 인자 별칭 템플릿 (별칭 템플릿) [edit] |
|---|
static_assert- SFINAE
- Constraints and Concepts (제약과 개념)