A priority_tag
-like pattern for type-based metaprogramming
priority_tag
-like pattern for type-based metaprogrammingVia Enrico Mauro, an interesting variation on the priority_tag
trick.
Compare the following example to the HasSomeKindOfSwap
example in
“priority_tag
for ad-hoc tag dispatch” (2021-07-09).
template<class T, int N = 2, class = void>
struct HasSomeKindOfValueType
: HasSomeKindOfValueType<T, N-1> {};
template<class T>
struct HasSomeKindOfValueType<T, 2, std::void_t<typename T::value_type>>
: std::true_type {};
template<class T>
struct HasSomeKindOfValueType<T, 1, std::void_t<typename T::element_type>>
: std::true_type {};
template<class T>
struct HasSomeKindOfValueType<T, 0>
: std::false_type {};
static_assert(HasSomeKindOfValueType<std::vector<int>>::value);
static_assert(HasSomeKindOfValueType<std::shared_ptr<int>>::value);
static_assert(!HasSomeKindOfValueType<int>::value);
This snippet cleanly expresses the logic: “If T::value_type
exists, return true; otherwise if T::element_type
exists,
return true; otherwise return false.” It combines two template-metaprogramming
tricks we’ve seen before:
-
The “recursive template,” where
X<2>
derives from (or calls)X<1>
, which derives from (or calls)X<0>
. -
The “
Enable
parameter,” where you hang an extraclass Enable = void
parameter off the end of your trait precisely so that it can be used for SFINAE.
The Enable
parameter isn’t technically needed in C++20; we have requires
for that, now.
The following C++20 expresses the same logic as the C++17 above.
template<class T, int N = 2>
struct HasSomeKindOfValueType
: HasSomeKindOfValueType<T, N-1> {};
template<class T> requires requires { typename T::value_type; }
struct HasSomeKindOfValueType<T, 2>
: std::true_type {};
template<class T> requires requires { typename T::element_type; }
struct HasSomeKindOfValueType<T, 1>
: std::true_type {};
template<class T>
struct HasSomeKindOfValueType<T, 0>
: std::false_type {};
See also:
- “Why do we require
requires requires
?” (2019-01-15)