Skip to content

Commit 0006b57

Browse files
authored
Merge pull request #411 from elbeno/filtered-index
✨ Add `filtered_index`
2 parents ddaaa70 + 6423138 commit 0006b57

9 files changed

Lines changed: 176 additions & 2 deletions

File tree

CMakeLists.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ target_sources(
7272
include/stdx/intrusive_list.hpp
7373
include/stdx/iterator.hpp
7474
include/stdx/latched.hpp
75+
include/stdx/meta.hpp
7576
include/stdx/numeric.hpp
7677
include/stdx/optional.hpp
7778
include/stdx/panic.hpp
@@ -89,6 +90,10 @@ target_sources(
8990
include/stdx/utility.hpp)
9091

9192
if(PROJECT_IS_TOP_LEVEL)
93+
set_target_properties(stdx PROPERTIES VERIFY_INTERFACE_HEADER_SETS true)
94+
add_dependencies(${INFRA_TARGET_NAMESPACE}quality
95+
stdx_verify_interface_header_sets)
96+
9297
include(CTest)
9398
add_docs(docs)
9499
add_subdirectory(test)

docs/design/meta.md

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
# meta
2+
3+
Source code: https://github.com/intel/cpp-std-extensions/blob/main/include/stdx/meta.hpp
4+
Documentation: https://intel.github.io/cpp-std-extensions/#_meta_hpp
5+
6+
## Filtering out `void`
7+
8+
`filtered_index` solves a subproblem of aggregating function results. You have a
9+
list of functions:
10+
11+
```cpp
12+
// f0 :: () -> int
13+
// f1 :: () -> void
14+
// f2 :: () -> int
15+
16+
using func_list_t = std::tuple<F0, F1, F2>;
17+
```
18+
19+
Some of those functions return `void`. It is easy enough to make a `tuple` of
20+
results with the `void` elements filtered out:
21+
22+
```cpp
23+
template <typename F>
24+
using returns_void = std::is_void<std::invoke_result_t<F>>;
25+
26+
using non_void_func_list_t = boost::mp11::mp_remove_if<func_list_t, returns_void>;
27+
// -> std::tuple<F0, F2>
28+
29+
using results_t = boost::mp11::mp_transform<std::invoke_result_t, non_void_func_list_t>;
30+
// -> std::tuple<int, int>
31+
```
32+
33+
The problem is: after running say `f2` and getting the result, where in the
34+
results tuple should it be placed? This is what `filtered_index` answers: where
35+
does the original index corresponding to `f2` map to in the filtered list?
36+
37+
If this problem seems solvable a simpler way, imagine that the functions are
38+
asynchronous, we need to provision the results tuple space up front, and handle
39+
asynchronous placement of the results as they come in. We need to deal with
40+
indices because any of the function types or result types might coincide, i.e.
41+
we can't find them reliably by type.

docs/header_graph.mmd

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,15 @@ flowchart BT
66
array(<a href="https://github.com/intel/cpp-std-extensions/tree/main/include/stdx/array.hpp">array.hpp</a>)
77
atomic(<a href="https://github.com/intel/cpp-std-extensions/tree/main/include/stdx/atomic.hpp">atomic.hpp</a>)
88
ct_conversions(<a href="https://github.com/intel/cpp-std-extensions/tree/main/include/stdx/ct_conversions.hpp">ct_conversions.hpp</a>)
9-
priority(<a href="https://github.com/intel/cpp-std-extensions/tree/main/include/stdx/priority.hpp">priority.hpp</a>)
9+
meta(<a href="https://github.com/intel/cpp-std-extensions/tree/main/include/stdx/meta.hpp">meta.hpp</a>)
1010
numeric(<a href="https://github.com/intel/cpp-std-extensions/tree/main/include/stdx/numeric.hpp">numeric.hpp</a>)
11+
priority(<a href="https://github.com/intel/cpp-std-extensions/tree/main/include/stdx/priority.hpp">priority.hpp</a>)
1112
array ~~~ compiler
1213
atomic ~~~ compiler
1314
ct_conversions --> compiler
14-
priority ~~~ compiler
15+
meta ~~~ compiler
1516
numeric ~~~ compiler
17+
priority ~~~ compiler
1618

1719
%% level 2
1820
type_traits(<a href="https://github.com/intel/cpp-std-extensions/tree/main/include/stdx/type_traits.hpp">type_traits.hpp</a>)

docs/index.adoc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ include::intrusive_forward_list.adoc[]
3333
include::intrusive_list.adoc[]
3434
include::iterator.adoc[]
3535
include::latched.adoc[]
36+
include::meta.adoc[]
3637
include::numeric.adoc[]
3738
include::optional.adoc[]
3839
include::panic.adoc[]

docs/intro.adoc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ The following compilers are supported:
1919
* clang 18
2020
* clang 19
2121
* clang 20
22+
* clang 21
2223
* clang 22
2324
* gcc 12
2425
* gcc 13
@@ -88,6 +89,7 @@ The following headers are available:
8889
* https://github.com/intel/cpp-std-extensions/blob/main/include/stdx/intrusive_list.hpp[`intrusive_list.hpp`]
8990
* https://github.com/intel/cpp-std-extensions/blob/main/include/stdx/iterator.hpp[`iterator.hpp`]
9091
* https://github.com/intel/cpp-std-extensions/blob/main/include/stdx/latched.hpp[`latched.hpp`]
92+
* https://github.com/intel/cpp-std-extensions/blob/main/include/stdx/meta.hpp[`meta.hpp`]
9193
* https://github.com/intel/cpp-std-extensions/blob/main/include/stdx/numeric.hpp[`numeric.hpp`]
9294
* https://github.com/intel/cpp-std-extensions/blob/main/include/stdx/optional.hpp[`optional.hpp`]
9395
* https://github.com/intel/cpp-std-extensions/blob/main/include/stdx/panic.hpp[`panic.hpp`]

docs/meta.adoc

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
2+
== `meta.hpp`
3+
4+
https://github.com/intel/cpp-std-extensions/blob/main/include/stdx/meta.hpp[`meta.hpp`]
5+
defines some metafunctions intended for compatibility with https://www.boost.org/libs/mp11[`boost::mp11`].
6+
7+
The metafunctions are in the `stdx::mp` namespace.
8+
9+
=== `filtered_index`
10+
11+
`filtered_index` answers the question: given a list and a predicate, if the list
12+
were filtered (as if by `boost::mp11::mp_copy_if`) by that predicate, where
13+
would an index in the unfiltered list map to in the filtered list?
14+
15+
`filtered_index_c` is like `filtered_index`, but instead of taking a
16+
`std::integral_constant`, it takes a plain integral type.
17+
18+
[source,cpp]
19+
----
20+
// list: (boost::mp_int<> of) 0, 1, 2, 3, 4
21+
using list_t = boost::mp11::mp_iota_c<5>;
22+
23+
// predicate
24+
template <typename T> using is_even = boost::mp11::mp_bool<T::value % 2 == 0>;
25+
26+
// filtered list would be (boost::mp_int<> of) 0, 2, 4
27+
using index_of_0 = stdx::mp::filtered_index_c<list_t, is_even, 0>;
28+
using index_of_2 = stdx::mp::filtered_index_c<list_t, is_even, 2>;
29+
using index_of_4 = stdx::mp::filtered_index_c<list_t, is_even, 4>;
30+
static_assert(std::same_as<index_of_0, boost::mp11::mp_size_t<0>>);
31+
static_assert(std::same_as<index_of_2, boost::mp11::mp_size_t<1>>);
32+
static_assert(std::same_as<index_of_4, boost::mp11::mp_size_t<2>>);
33+
34+
// filtered items don't exist in the list -- by boost convention, the index
35+
// returned is equal to the (filtered) list size
36+
using index_of_1 = stdx::mp::filtered_index_c<list_t, is_even, 1>;
37+
using index_of_3 = stdx::mp::filtered_index_c<list_t, is_even, 3>;
38+
static_assert(std::same_as<index_of_1, boost::mp11::mp_size_t<3>>);
39+
static_assert(std::same_as<index_of_3, boost::mp11::mp_size_t<3>>);
40+
----

include/stdx/meta.hpp

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
#pragma once
2+
3+
#include <boost/mp11/algorithm.hpp>
4+
#include <boost/mp11/function.hpp>
5+
#include <boost/mp11/integral.hpp>
6+
7+
#include <limits>
8+
9+
namespace stdx {
10+
inline namespace v1 {
11+
namespace mp {
12+
namespace detail {
13+
template <template <typename...> typename P> struct mask_bit_q {
14+
template <typename T>
15+
using fn = boost::mp11::mp_size_t<P<T>::value ? 1 : 0>;
16+
};
17+
18+
template <typename L, template <typename...> typename P>
19+
using list_mask_t = boost::mp11::mp_transform_q<mask_bit_q<P>, L>;
20+
21+
template <typename L, template <typename...> typename P>
22+
using list_mask_scan_t = boost::mp11::mp_partial_sum<
23+
list_mask_t<L, P>,
24+
boost::mp11::mp_size_t<std::numeric_limits<std::size_t>::max()>,
25+
boost::mp11::mp_plus>;
26+
} // namespace detail
27+
28+
template <typename L, template <typename...> typename P, typename I>
29+
using filtered_index =
30+
boost::mp11::mp_if<P<boost::mp11::mp_at<L, I>>,
31+
boost::mp11::mp_at<detail::list_mask_scan_t<L, P>, I>,
32+
boost::mp11::mp_size<boost::mp11::mp_copy_if<L, P>>>;
33+
34+
template <typename L, template <typename...> typename P, std::size_t I>
35+
using filtered_index_c = filtered_index<L, P, boost::mp11::mp_size_t<I>>;
36+
} // namespace mp
37+
} // namespace v1
38+
} // namespace stdx

test/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ add_tests(
5555
intrusive_list_properties
5656
iterator
5757
latched
58+
meta
5859
numeric
5960
optional
6061
overload

test/meta.cpp

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
#include <stdx/meta.hpp>
2+
3+
#include <boost/mp11/algorithm.hpp>
4+
#include <catch2/catch_test_macros.hpp>
5+
6+
namespace {
7+
using list_t = boost::mp11::mp_iota_c<5>;
8+
template <typename T> using is_even = std::bool_constant<T::value % 2 == 0>;
9+
} // namespace
10+
11+
TEST_CASE("filtered_index (items that pass the predicate)", "[meta]") {
12+
STATIC_CHECK(
13+
std::same_as<stdx::mp::filtered_index<list_t, is_even,
14+
boost::mp11::mp_size_t<0>>,
15+
boost::mp11::mp_size_t<0>>);
16+
STATIC_CHECK(
17+
std::same_as<stdx::mp::filtered_index<list_t, is_even,
18+
boost::mp11::mp_size_t<2>>,
19+
boost::mp11::mp_size_t<1>>);
20+
STATIC_CHECK(
21+
std::same_as<stdx::mp::filtered_index<list_t, is_even,
22+
boost::mp11::mp_size_t<4>>,
23+
boost::mp11::mp_size_t<2>>);
24+
}
25+
26+
TEST_CASE("filtered_index (items that fail the predicate)", "[meta]") {
27+
STATIC_CHECK(
28+
std::same_as<stdx::mp::filtered_index<list_t, is_even,
29+
boost::mp11::mp_size_t<1>>,
30+
boost::mp11::mp_size_t<3>>);
31+
STATIC_CHECK(
32+
std::same_as<stdx::mp::filtered_index<list_t, is_even,
33+
boost::mp11::mp_size_t<3>>,
34+
boost::mp11::mp_size_t<3>>);
35+
}
36+
37+
TEST_CASE("filtered_index_c", "[meta]") {
38+
STATIC_CHECK(std::same_as<stdx::mp::filtered_index_c<list_t, is_even, 0>,
39+
boost::mp11::mp_size_t<0>>);
40+
STATIC_CHECK(std::same_as<stdx::mp::filtered_index_c<list_t, is_even, 2>,
41+
boost::mp11::mp_size_t<1>>);
42+
STATIC_CHECK(std::same_as<stdx::mp::filtered_index_c<list_t, is_even, 4>,
43+
boost::mp11::mp_size_t<2>>);
44+
}

0 commit comments

Comments
 (0)