Abseil type_traits — negation·conjunction·void_t
한 줄 요약:
absl::negation,absl::conjunction,absl::disjunction,absl::void_t는 C++17이 표준화한 type_traits 유틸리티의 C++14 polyfill이다. short-circuit evaluation과 SFINAE 친화성이 핵심이다.
#어떤 문제를 푸는가
template metaprogramming에서 “조건을 합치는” 작업은 흔하다.
template <typename T>std::enable_if_t<std::is_integral_v<T> && !std::is_same_v<T, bool>, void>DoSomething(T t);&&로 직접 묶는 방식은 C++14까지는 SFINAE에서 short-circuit이 보장되지 않았다. 즉, 첫 조건이 거짓이어도 두 번째 조건이 instantiated되어 hard error를 일으킬 수 있다. C++17의 std::conjunction이 이 문제를 해결했고, absl::conjunction은 C++14에서도 같은 동작을 제공한다.
#absl::negation — 부정
template <typename T>struct negation : std::integral_constant<bool, !T::value> {};std::negation (C++17)과 동일.
template <typename T>using is_not_void = absl::negation<std::is_void<T>>;
static_assert(is_not_void<int>::value);static_assert(!is_not_void<void>::value);! 한 번 쓰는 것과 차이는 없어 보이지만, 다른 traits와 조합할 때 의미가 살아난다.
#absl::conjunction — AND (short-circuit)
template <typename... Ts>struct conjunction : std::true_type {};
template <typename T>struct conjunction<T> : T {};
template <typename T, typename... Ts>struct conjunction<T, Ts...> : std::conditional_t<bool(T::value), conjunction<Ts...>, T> {};핵심은 std::conditional_t다. 첫 조건이 false면 나머지를 instantiate하지 않는다.
// 위험한 패턴 — &&로 합치면 두 번째가 항상 instantiatedtemplate <typename T>std::enable_if_t< sizeof(T) > 0 && HasIteratorTrait<T>::value, void> Process(T t);
// 안전한 방식 — conjunction으로 short-circuittemplate <typename T>std::enable_if_t< absl::conjunction< std::integral_constant<bool, (sizeof(T) > 0)>, HasIteratorTrait<T> >::value, void> Process(T t);#absl::disjunction — OR (short-circuit)
template <typename... Ts>struct disjunction : std::false_type {};
template <typename T>struct disjunction<T> : T {};
template <typename T, typename... Ts>struct disjunction<T, Ts...> : std::conditional_t<bool(T::value), T, disjunction<Ts...>> {};대칭 구조다. 첫 조건이 true면 나머지를 instantiate하지 않는다.
template <typename T>using is_numeric = absl::disjunction< std::is_integral<T>, std::is_floating_point<T>>;
static_assert(is_numeric<int>::value);static_assert(is_numeric<double>::value);static_assert(!is_numeric<std::string>::value);#absl::void_t — SFINAE 핵심 도구
template <typename...>using void_t = void;단순해 보이지만 SFINAE의 가장 강력한 도구 중 하나. “이 type 표현식이 valid한가”를 검사한다.
// T가 iterator를 가졌는가?template <typename T, typename = void>struct has_iterator : std::false_type {};
template <typename T>struct has_iterator<T, absl::void_t<typename T::iterator>> : std::true_type {};
static_assert(has_iterator<std::vector<int>>::value);static_assert(!has_iterator<int>::value);작동 원리:
- primary template은
false_type상속. - partial specialization이
T::iterator를 시도. 성공하면void로 evaluation되어 specialization 선택. - 실패하면 SFINAE로 primary template fallback.
#더 복잡한 예시 — detection idiom
// T가 begin()을 가졌는가?template <typename T, typename = void>struct has_begin : std::false_type {};
template <typename T>struct has_begin<T, absl::void_t<decltype(std::declval<T>().begin())>> : std::true_type {};
// begin()과 end()를 모두 가졌는가?template <typename T, typename = void>struct is_iterable : std::false_type {};
template <typename T>struct is_iterable<T, absl::void_t< decltype(std::declval<T>().begin()), decltype(std::declval<T>().end())>> : std::true_type {};void_t는 가변 인자 template이므로 여러 표현식을 한꺼번에 검사할 수 있다.
#std와의 비교
C++17 이후로는 표준 type_traits가 같은 기능을 제공한다.
| Abseil | C++17 std |
|---|---|
absl::negation<T> | std::negation<T> |
absl::conjunction<Ts...> | std::conjunction<Ts...> |
absl::disjunction<Ts...> | std::disjunction<Ts...> |
absl::void_t<Ts...> | std::void_t<Ts...> |
C++14를 타깃하는 코드에서는 Abseil 버전이 필요. C++17 이상이면 std로 옮기는 것이 권장된다.
// 권장 마이그레이션// beforeusing has_x = absl::conjunction<HasFoo<T>, HasBar<T>>;
// after (C++17+)using has_x = std::conjunction<HasFoo<T>, HasBar<T>>;#absl 추가 traits
표준에 없는 traits도 있다.
#type_identity
C++20에서 표준화된 type_identity의 polyfill.
template <typename T>struct type_identity { using type = T; };
template <typename T>void Print(T value, typename absl::type_identity<T>::type other);// 두 인자가 다른 type이면 첫 인자만 deduction#is_trivially_*
오래된 컴파일러에서 부정확한 std::is_trivially_*를 컴파일러 builtin으로 직접 구현. 지금은 대부분의 컴파일러가 표준 traits를 정확히 구현하므로 사용 빈도 감소.
#코드 리뷰 포인트
// 회피 — &&로 SFINAE 조건을 합침 (C++14)template <typename T>std::enable_if_t< std::is_integral_v<T> && (sizeof(T) > 4), void> Process(T t);
// Good — conjunctiontemplate <typename T>std::enable_if_t< absl::conjunction< std::is_integral<T>, std::integral_constant<bool, (sizeof(T) > 4)> >::value, void> Process(T t);// 회피 — detection을 매번 손으로template <typename T>auto HasBegin(int) -> decltype(std::declval<T>().begin(), std::true_type{});template <typename T>auto HasBegin(...) -> std::false_type;
// Good — void_t 패턴template <typename T, typename = void>struct HasBegin : std::false_type {};
template <typename T>struct HasBegin<T, absl::void_t<decltype(std::declval<T>().begin())>> : std::true_type {};리뷰에서:
- C++ 표준 버전이 무엇인가 — C++17+이면 std로 옮길 것.
- SFINAE에서 &&를 쓰는가 — short-circuit이 필요하면 conjunction.
- detection idiom을 재발명하는가 — void_t 패턴 권장.
#자주 보는 안티패턴
// 회피 — concept이 있는데도 conjunction 사용 (C++20)template <typename T>requires absl::conjunction<...>::valuevoid Process(T t);
// Good — concept 직접 사용template <typename T>requires std::integral<T>void Process(T t);// 회피 — type_traits를 macro로 감쌈#define HAS_BEGIN(T) HasBegin<T>::valueif constexpr (HAS_BEGIN(MyType)) { ... }// 매크로보다 traits를 직접 쓰는 게 디버깅에 유리.#정리
absl::conjunction,absl::disjunction은 short-circuit AND/OR. C++17std::conjunction의 polyfill.absl::negation은 traits 부정.absl::void_t는 detection idiom의 핵심. “이 표현식이 valid한가”를 검사.- C++17 이상이면 std로 옮길 것.
- C++20 concept이 있으면 traits보다 우선.
#다음 편
Part 2-05에서 Abseil의 conformance / policy 매크로를 본다. ABSL_DEPRECATED_IF_UNAVAILABLE 같은 정책 매크로가 어떻게 마이그레이션을 점진적으로 가능하게 하는지.
#관련 항목
Abseil Code Review · 10 of 79
- 1 Abseil Code Review — Google production-grade C++ 라이브러리 분석
- 2 Abseil 개요 — Google이 std를 보완한 이유
- 3 Abseil 설계 철학 — std 호환과 추가 기능의 균형
- 4 Abseil 빌드와 의존성 — Bazel vs CMake
- 5 Abseil LTS vs HEAD 릴리스 모델 분석
- 6 Abseil Versioning과 ABI 호환성 정책
- 7 Abseil 매크로 — ABSL_HAVE_*·ABSL_ATTRIBUTE_*
- 8 Abseil ABSL_PREDICT_TRUE/FALSE — branch hint
- 9 absl::LogSeverity — 로그 레벨 타입
- 10 Abseil type_traits — negation·conjunction·void_t
- 11 Abseil Conformance·Policy 분석
- 12 Abseil Memory utilities 분석
- 13 Abseil raw_logging — heap-free 로깅
- 14 Abseil thread_annotations — clang TSA 통합
- 15 absl::Status — exception-free error handling
- 16 absl::StatusOr<T> — 값 또는 에러
- 17 absl status_macros — ASSIGN_OR_RETURN·RETURN_IF_ERROR
- 18 absl::Status payload — 구조화된 에러 컨텍스트
- 19 absl::Status ↔ exception 변환 패턴
- 20 absl::string_view — non-owning 문자열 참조
- 21 absl::string_view 함정 — dangling·c_str·임시 객체
- 22 absl::StrCat — 가변 인자 문자열 연결과 AlphaNum
- 23 absl::StrSplit — Delimiter·Predicate·컨테이너 변환
- 24 absl::StrJoin — 컨테이너 결합과 Formatter
- 25 absl::StrFormat — type-safe printf·FormatSpec
- 26 Abseil ASCII 함수 — locale-free 분류·대소문자 변환
- 27 Abseil Escape — CEscape·HexEscape·Base64
- 28 absl::flat_hash_map — Swiss Table 기반 hash map
- 29 absl::flat_hash_set — set 버전 Swiss Table
- 30 absl::node_hash_map — stable pointer가 필요할 때
- 31 absl::btree_map — sorted·cache-friendly B-tree
- 32 absl::FixedArray — 런타임 크기 stack 배열
- 33 absl::InlinedVector — small buffer optimization
- 34 Abseil Swiss Table internals — control byte·SIMD probing
- 35 absl::Mutex — reader-writer·fairness·deadlock 검출
- 36 absl::Mutex Conditional Critical Section — Await로 cv 없애기
- 37 absl::Notification — once-only signal
- 38 absl::BlockingCounter·Barrier — 다중 thread 조율
- 39 absl::Mutex annotations — clang thread-safety로 race를 컴파일 타임에
- 40 absl::Time·Duration 분석 — 단단한 type
- 41 absl::Time Format·Parse
- 42 absl::CivilTime 분석
- 43 absl::time_zone 분석
- 44 absl::Time mocking — 테스트 친화 시간
- 45 absl::BitGen — 모던 난수 생성기
- 46 Abseil Random Distributions — Uniform·Exponential
- 47 Abseil Mocking Random — 테스트 결정성
- 48 Abseil Random Seeding·Entropy
- 49 absl::int128·uint128 분석
- 50 absl::bits — popcount·countl_zero
- 51 absl::optional vs std::optional
- 52 absl::variant 분석
- 53 absl::span 분석
- 54 absl::any 분석
- 55 absl::compare — three-way 비교
- 56 Abseil utility — apply·in_place
- 57 Abseil AbslHashValue 분석
- 58 Abseil HashState chaining
- 59 Abseil Custom hashable 구현
- 60 Abseil LOG·VLOG·CHECK 분석
- 61 Abseil LogSink 분석
- 62 Abseil LogEntry·structured logging
- 63 Abseil Stack trace·failure_signal_handler
- 64 ABSL_FLAG 정의 분석
- 65 Abseil ParseCommandLine 동작
- 66 Abseil Flag introspection·validation
- 67 Google 스타일의 Abseil 사용 패턴
- 68 Abseil 자주 보는 anti-pattern
- 69 std → absl 마이그레이션 전략
- 70 absl::Cleanup — 함수 종료 시 실행 보장
- 71 Abseil algorithm container 확장 — c_sort·c_find_if·c_count_if
- 72 absl::function_ref와 any_invocable — 함수 객체 전달의 두 축
- 73 absl::bind_front와 Overload — 함수 객체 보조 도구
- 74 absl::Cord — 분산 시스템용 대용량 문자열
- 75 absl::from_chars·SimpleAtoi — 빠른 숫자 변환
- 76 absl::Cord vs std::string — 선택 기준과 메모리 프로파일
- 77 absl::GetStackTrace와 Symbolize — crash 시 readable stack
- 78 absl::ComputeCrc32c — 하드웨어 가속 체크섬
- 79 absl::PeriodicSampler — 적응형 샘플링·jitter 회피
관련 글
absl::PeriodicSampler — 적응형 샘플링·jitter 회피
absl::profiling_internal::PeriodicSampler — sampling rate를 동적으로 조정, geometric distribution으로 jitter 회피. 메모리 할당 추적·profiling 인프라의 기반.
같은 시리즈에서 이어 읽기
absl::ComputeCrc32c — 하드웨어 가속 체크섬
absl::ComputeCrc32c — SSE4.2 CRC32, ARM CRC 명령어로 가속된 CRC32C 구현. iSCSI·Btrfs·protobuf에서 표준화된 무결성 검사.
같은 시리즈에서 이어 읽기
absl::GetStackTrace와 Symbolize — crash 시 readable stack
absl::GetStackTrace로 PC 배열을 받고 absl::Symbolize로 함수 이름·파일·라인으로 변환. signal handler 안에서도 동작하는 async-safe API.
같은 시리즈에서 이어 읽기