41. Trait Detection
In a low-latency gateway, we often introspect message types at compile time to select zero-overhead fast paths. Type traits and detection let us enforce uniform interfaces without virtuals or runtime branches. This snippet detects an expected API shape at compile time.
#include <type_traits>
#include <utility>
template<class T, class = void> struct has_seq : std::false_type {};
template<class T> struct has_seq<T, std::void_t<decltype(std::declval<T&>().seq)>> : std::true_type {};
struct Msg { int seq; };
static_assert(has_seq<Msg>::value);
Part 1.
Precisely explain what has_seq detects and why the static_assert passes. How would you extend the trait to also accept a seq() const accessor returning an integral, and then provide a zero-overhead get_seq(T const&) that works for both forms?
Part 2.
(1) Prefer SFINAE or C++20 concepts here? Consider diagnostics, readability, and constraints.
(2) When use if constexpr versus overload sets for zero-cost selection and inlining control?
(3) How can noexcept and traits direct move vs copy choices in hot paths?
(4) Is std::is_trivially_copyable_v<T> sufficient for memcpy safety given strict aliasing rules?
(5) How to limit template bloat and ODR issues from widespread trait instantiations across translation units?
Answer
Answer (Part 1)
has_seq uses partial specialization with std::void_t to detect any accessible member named seq (data member or member function) in an unevaluated context, so it returns true for struct Msg { int seq; }. To also accept an accessor, define another trait like has_seq_fn<T> using std::void_t<decltype(std::declval<T const&>().seq())> constrained to std::is_integral_v<decltype(std::declval<T const&>().seq())>, then combine: has_sequence<T> = has_seq<T>::value || has_seq_fn<T>::value; implement get_seq(T const& x) with if constexpr (has_seq<T>::value) return x.seq; else return x.seq(); ensuring compile-time dispatch and full inlining.
Answer (Part 2)
(1) Concepts express intent and yield clearer diagnostics; prefer them in C++20+. SFINAE remains viable for back-compat and nuanced fallbacks.
(2) Use if constexpr when one body simplifies optimization and reduces duplication. Prefer overloads when separate instantiations aid code size or specialization.
(3) Traits like std::is_nothrow_move_constructible_v<T> plus noexcept-qualified overloads bias selection toward cheaper, safe moves. This avoids exception slow paths.
(4) Trivially copyable permits byte-wise copying of the object representation. It does not relax strict aliasing; alias legally or use std::byte.
(5) Centralize detection primitives and reuse them to curb instantiations. Employ inline variables/constexpr functions in headers to avoid multiple definitions.