From cb82b3d47a86546d39f7687d1d05d934460e9b8a Mon Sep 17 00:00:00 2001 From: Ben Deane Date: Tue, 25 Jun 2024 16:26:59 -0600 Subject: [PATCH] :new: Add `apply_sequence` Problem: - Expanding a type list into a pack of template arguments involves too much call-site boilerplate. Solution: - Add `apply_sequence` which is very similar to `template_for_each`, but instead of iterating the list, it passes the list contents as a pack. --- docs/type_traits.adoc | 92 ++++++++++++++++++++++++------------ include/stdx/type_traits.hpp | 35 +++++++++++++- test/type_traits.cpp | 25 ++++++++++ 3 files changed, 122 insertions(+), 30 deletions(-) diff --git a/docs/type_traits.adoc b/docs/type_traits.adoc index 0c6016c..5b52a5a 100644 --- a/docs/type_traits.adoc +++ b/docs/type_traits.adoc @@ -36,6 +36,38 @@ auto f(T) { }; ---- +=== `apply_sequence` + +A xref:type_traits.adoc#_type_list_and_value_list[`type_list` or a `value_list`] +can be unpacked and passed as individual template arguments with +`apply_sequence`. A function object whose call operator is a variadic function +template with no runtime arguments is called with the pack of arguments. + +[source,cpp] +---- +using L1 = stdx::type_list, + std::integral_constant>; +int x = stdx::apply_sequence([&] () { return (0 + ... + Ts::value); }); +// x is 3 + +using L2 = stdx::value_list<1, 2>; +int y = stdx::apply_sequence([&] () { return (0 + ... + Vs); }); +// y is 3 +---- + +`apply_sequence` can also be used with a +https://en.cppreference.com/w/cpp/utility/integer_sequence[`std::integer_sequence`]: + +[source,cpp] +---- +using L3 = stdx::make_index_sequence<3>; +auto y = stdx::apply_sequence([&] () { y += V; }); +// y is 3 +---- + +NOTE: If the function iterates the pack by folding over `operator,` then +xref:type_traits.adoc#_template_for_each[`template_for_each`] is probably what you want. + === `is_function_object_v` `is_function_object_v` is a variable template that detects whether a type is a @@ -52,6 +84,17 @@ stdx::is_function_object_v; // true stdx::is_function_object_v; // true ---- +=== `is_same_unqualified_v` + +`is_same_unqualified_v` is a variable template that detects whether a two types +are the same are removing top-level cv-qualifications and references, if any. + +[source,cpp] +---- +stdx::is_same_unqualified_v; // true +stdx::is_same_unqualified_v; // false +---- + === `is_specialization_of_v` `is_specialization_of_v` is a variable template that detects whether a type is a @@ -112,31 +155,12 @@ NOTE: Detecting structurality of a type is not yet possible in the general case, so there are certain structural types for which this trait will be `false`. In practice those types should be rare, and there should be no false positives. -=== `type_or_t` - -`type_or_t` is an alias template that selects a type based on whether or not it -passes a predicate. If not, a default is returned. - -[source,cpp] ----- -using A = int *; -using T = stdx::type_or_t; // A - -using B = int; -using X = stdx::type_or_t; // void (implicit default) -using Y = stdx::type_or_t; // float (explicit default) ----- - -=== `type_list` and `value_list` - -`type_list` is an empty `struct` templated over any number of types. -`value_list` is an empty `struct` templated over any number of NTTPs. - === `template_for_each` -A `type_list` or a `value_list` can be iterated with `template_for_each`. A -function object whose call operator is a unary function template with no runtime -arguments is passed to each of these functions. +A xref:type_traits.adoc#_type_list_and_value_list[`type_list` or a `value_list`] +can be iterated with `template_for_each`. A function object whose call operator +is a unary function template with no runtime arguments is called with each +element of the list. [source,cpp] ---- @@ -158,7 +182,7 @@ https://en.cppreference.com/w/cpp/utility/integer_sequence[`std::integer_sequenc [source,cpp] ---- using L3 = stdx::make_index_sequence<3>; -int y{}; +std::size_t y{}; stdx::template_for_each([&] () { y += V; }); // y is now 3 ---- @@ -166,13 +190,23 @@ stdx::template_for_each([&] () { y += V; }); NOTE: A primary use case of `template_for_each` is to be able to use a list of tag types without those types having to be complete. -=== `is_same_unqualified_v` +=== `type_or_t` -`is_same_unqualified_v` is a variable template that detects whether a two types -are the same are removing top-level cv-qualifications and references, if any. +`type_or_t` is an alias template that selects a type based on whether or not it +passes a predicate. If not, a default is returned. [source,cpp] ---- -stdx::is_same_unqualified_v; // true -stdx::is_same_unqualified_v; // false +using A = int *; +using T = stdx::type_or_t; // A + +using B = int; +using X = stdx::type_or_t; // void (implicit default) +using Y = stdx::type_or_t; // float (explicit default) ---- + +=== `type_list` and `value_list` + +`type_list` is an empty `struct` templated over any number of types. +`value_list` is an empty `struct` templated over any number of NTTPs. + diff --git a/include/stdx/type_traits.hpp b/include/stdx/type_traits.hpp index 0f62a16..0646e39 100644 --- a/include/stdx/type_traits.hpp +++ b/include/stdx/type_traits.hpp @@ -118,6 +118,7 @@ constexpr bool is_scoped_enum_v = template struct type_list {}; template struct value_list {}; +namespace detail { template struct for_each_t { static_assert(always_false_v, "template_for_each must be called with a type list, " @@ -142,8 +143,40 @@ struct for_each_t> { (f.template operator()(), ...); } }; +} // namespace detail + +template +constexpr static auto template_for_each = detail::for_each_t{}; + +namespace detail { +template struct apply_sequence_t { + static_assert(always_false_v, + "apply_sequence must be called with a type list, " + "value_list, or std::integer_sequence"); +}; + +template