Skip to content

Commit

Permalink
Merge pull request #120 from elbeno/apply-sequence
Browse files Browse the repository at this point in the history
🆕 Add `apply_sequence`
  • Loading branch information
elbeno authored Jun 25, 2024
2 parents 54abfb1 + cb82b3d commit 2677ef6
Show file tree
Hide file tree
Showing 3 changed files with 122 additions and 30 deletions.
92 changes: 63 additions & 29 deletions docs/type_traits.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -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, 1>,
std::integral_constant<int, 2>>;
int x = stdx::apply_sequence<L1>([&] <typename... Ts> () { return (0 + ... + Ts::value); });
// x is 3
using L2 = stdx::value_list<1, 2>;
int y = stdx::apply_sequence<L1>([&] <auto... Vs> () { 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<L3>([&] <auto... Vs> () { 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
Expand All @@ -52,6 +84,17 @@ stdx::is_function_object_v<decltype(lam)>; // true
stdx::is_function_object_v<decltype(gen_lam)>; // 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<int, int const&>; // true
stdx::is_same_unqualified_v<int, void>; // false
----

=== `is_specialization_of_v`

`is_specialization_of_v` is a variable template that detects whether a type is a
Expand Down Expand Up @@ -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<std::is_pointer, A>; // A
using B = int;
using X = stdx::type_or_t<std::is_pointer, B>; // void (implicit default)
using Y = stdx::type_or_t<std::is_pointer, B, float>; // 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]
----
Expand All @@ -158,21 +182,31 @@ 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<L3>([&] <auto V> () { y += V; });
// y is now 3
----

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<int, int const&>; // true
stdx::is_same_unqualified_v<int, void>; // false
using A = int *;
using T = stdx::type_or_t<std::is_pointer, A>; // A
using B = int;
using X = stdx::type_or_t<std::is_pointer, B>; // void (implicit default)
using Y = stdx::type_or_t<std::is_pointer, B, float>; // 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.

35 changes: 34 additions & 1 deletion include/stdx/type_traits.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ constexpr bool is_scoped_enum_v =
template <typename...> struct type_list {};
template <auto...> struct value_list {};

namespace detail {
template <typename L> struct for_each_t {
static_assert(always_false_v<L>,
"template_for_each must be called with a type list, "
Expand All @@ -142,8 +143,40 @@ struct for_each_t<L<T, Vs...>> {
(f.template operator()<Vs>(), ...);
}
};
} // namespace detail

template <typename L>
constexpr static auto template_for_each = detail::for_each_t<L>{};

namespace detail {
template <typename L> struct apply_sequence_t {
static_assert(always_false_v<L>,
"apply_sequence must be called with a type list, "
"value_list, or std::integer_sequence");
};

template <template <typename...> typename L, typename... Ts>
struct apply_sequence_t<L<Ts...>> {
template <typename F> constexpr auto operator()(F &&f) const {
return f.template operator()<Ts...>();
}
};
template <template <auto...> typename L, auto... Vs>
struct apply_sequence_t<L<Vs...>> {
template <typename F> constexpr auto operator()(F &&f) const {
return f.template operator()<Vs...>();
}
};
template <template <typename, auto...> typename L, typename T, T... Vs>
struct apply_sequence_t<L<T, Vs...>> {
template <typename F> constexpr auto operator()(F &&f) const {
return f.template operator()<Vs...>();
}
};
} // namespace detail

template <typename L> constexpr static auto template_for_each = for_each_t<L>{};
template <typename L>
constexpr static auto apply_sequence = detail::apply_sequence_t<L>{};

template <typename T, typename U>
constexpr bool is_same_unqualified_v =
Expand Down
25 changes: 25 additions & 0 deletions test/type_traits.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,15 @@ struct add_value {
}
template <auto T> constexpr auto operator()() const -> void { value += T; }
};

struct add_values {
template <typename... Ts> constexpr auto operator()() const {
return (0 + ... + Ts::value);
}
template <auto... Ts> constexpr auto operator()() const {
return (0 + ... + Ts);
}
};
} // namespace

TEST_CASE("template_for_each with type list", "[type_traits]") {
Expand Down Expand Up @@ -138,6 +147,22 @@ TEST_CASE("template_for_each with index sequence", "[type_traits]") {
CHECK(value == 3);
}

TEST_CASE("apply_sequence with type list", "[type_traits]") {
using L = stdx::type_list<std::integral_constant<int, 1>,
std::integral_constant<int, 2>>;
CHECK(stdx::apply_sequence<L>(add_values{}) == 3);
}

TEST_CASE("apply_sequence with value list", "[type_traits]") {
using L = stdx::value_list<1, 2>;
CHECK(stdx::apply_sequence<L>(add_values{}) == 3);
}

TEST_CASE("apply_sequence with index sequence", "[type_traits]") {
using L = std::make_index_sequence<3>;
CHECK(stdx::apply_sequence<L>(add_values{}) == 3);
}

TEST_CASE("is_same_unqualified", "[type_traits]") {
static_assert(stdx::is_same_unqualified_v<int, int>);
static_assert(not stdx::is_same_unqualified_v<int, void>);
Expand Down

0 comments on commit 2677ef6

Please sign in to comment.