Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ target_sources(
include/stdx/intrusive_list.hpp
include/stdx/iterator.hpp
include/stdx/latched.hpp
include/stdx/meta.hpp
include/stdx/numeric.hpp
include/stdx/optional.hpp
include/stdx/panic.hpp
Expand All @@ -89,6 +90,10 @@ target_sources(
include/stdx/utility.hpp)

if(PROJECT_IS_TOP_LEVEL)
set_target_properties(stdx PROPERTIES VERIFY_INTERFACE_HEADER_SETS true)
add_dependencies(${INFRA_TARGET_NAMESPACE}quality
stdx_verify_interface_header_sets)

include(CTest)
add_docs(docs)
add_subdirectory(test)
Expand Down
41 changes: 41 additions & 0 deletions docs/design/meta.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# meta

Source code: https://github.com/intel/cpp-std-extensions/blob/main/include/stdx/meta.hpp
Documentation: https://intel.github.io/cpp-std-extensions/#_meta_hpp

## Filtering out `void`

`filtered_index` solves a subproblem of aggregating function results. You have a
list of functions:

```cpp
// f0 :: () -> int
// f1 :: () -> void
// f2 :: () -> int

using func_list_t = std::tuple<F0, F1, F2>;
```

Some of those functions return `void`. It is easy enough to make a `tuple` of
results with the `void` elements filtered out:

```cpp
template <typename F>
using returns_void = std::is_void<std::invoke_result_t<F>>;

using non_void_func_list_t = boost::mp11::mp_remove_if<func_list_t, returns_void>;
// -> std::tuple<F0, F2>

using results_t = boost::mp11::mp_transform<std::invoke_result_t, non_void_func_list_t>;
// -> std::tuple<int, int>
```

The problem is: after running say `f2` and getting the result, where in the
results tuple should it be placed? This is what `filtered_index` answers: where
does the original index corresponding to `f2` map to in the filtered list?

If this problem seems solvable a simpler way, imagine that the functions are
asynchronous, we need to provision the results tuple space up front, and handle
asynchronous placement of the results as they come in. We need to deal with
indices because any of the function types or result types might coincide, i.e.
we can't find them reliably by type.
6 changes: 4 additions & 2 deletions docs/header_graph.mmd
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,15 @@ flowchart BT
array(<a href="https://github.com/intel/cpp-std-extensions/tree/main/include/stdx/array.hpp">array.hpp</a>)
atomic(<a href="https://github.com/intel/cpp-std-extensions/tree/main/include/stdx/atomic.hpp">atomic.hpp</a>)
ct_conversions(<a href="https://github.com/intel/cpp-std-extensions/tree/main/include/stdx/ct_conversions.hpp">ct_conversions.hpp</a>)
priority(<a href="https://github.com/intel/cpp-std-extensions/tree/main/include/stdx/priority.hpp">priority.hpp</a>)
meta(<a href="https://github.com/intel/cpp-std-extensions/tree/main/include/stdx/meta.hpp">meta.hpp</a>)
numeric(<a href="https://github.com/intel/cpp-std-extensions/tree/main/include/stdx/numeric.hpp">numeric.hpp</a>)
priority(<a href="https://github.com/intel/cpp-std-extensions/tree/main/include/stdx/priority.hpp">priority.hpp</a>)
array ~~~ compiler
atomic ~~~ compiler
ct_conversions --> compiler
priority ~~~ compiler
meta ~~~ compiler
numeric ~~~ compiler
priority ~~~ compiler

%% level 2
type_traits(<a href="https://github.com/intel/cpp-std-extensions/tree/main/include/stdx/type_traits.hpp">type_traits.hpp</a>)
Expand Down
1 change: 1 addition & 0 deletions docs/index.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ include::intrusive_forward_list.adoc[]
include::intrusive_list.adoc[]
include::iterator.adoc[]
include::latched.adoc[]
include::meta.adoc[]
include::numeric.adoc[]
include::optional.adoc[]
include::panic.adoc[]
Expand Down
2 changes: 2 additions & 0 deletions docs/intro.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ The following compilers are supported:
* clang 18
* clang 19
* clang 20
* clang 21
* clang 22
* gcc 12
* gcc 13
Expand Down Expand Up @@ -88,6 +89,7 @@ The following headers are available:
* https://github.com/intel/cpp-std-extensions/blob/main/include/stdx/intrusive_list.hpp[`intrusive_list.hpp`]
* https://github.com/intel/cpp-std-extensions/blob/main/include/stdx/iterator.hpp[`iterator.hpp`]
* https://github.com/intel/cpp-std-extensions/blob/main/include/stdx/latched.hpp[`latched.hpp`]
* https://github.com/intel/cpp-std-extensions/blob/main/include/stdx/meta.hpp[`meta.hpp`]
* https://github.com/intel/cpp-std-extensions/blob/main/include/stdx/numeric.hpp[`numeric.hpp`]
* https://github.com/intel/cpp-std-extensions/blob/main/include/stdx/optional.hpp[`optional.hpp`]
* https://github.com/intel/cpp-std-extensions/blob/main/include/stdx/panic.hpp[`panic.hpp`]
Expand Down
40 changes: 40 additions & 0 deletions docs/meta.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@

== `meta.hpp`

https://github.com/intel/cpp-std-extensions/blob/main/include/stdx/meta.hpp[`meta.hpp`]
defines some metafunctions intended for compatibility with https://www.boost.org/libs/mp11[`boost::mp11`].

The metafunctions are in the `stdx::mp` namespace.

=== `filtered_index`

`filtered_index` answers the question: given a list and a predicate, if the list
were filtered (as if by `boost::mp11::mp_copy_if`) by that predicate, where
would an index in the unfiltered list map to in the filtered list?

`filtered_index_c` is like `filtered_index`, but instead of taking a
`std::integral_constant`, it takes a plain integral type.

[source,cpp]
----
// list: (boost::mp_int<> of) 0, 1, 2, 3, 4
using list_t = boost::mp11::mp_iota_c<5>;

// predicate
template <typename T> using is_even = boost::mp11::mp_bool<T::value % 2 == 0>;

// filtered list would be (boost::mp_int<> of) 0, 2, 4
using index_of_0 = stdx::mp::filtered_index_c<list_t, is_even, 0>;
using index_of_2 = stdx::mp::filtered_index_c<list_t, is_even, 2>;
using index_of_4 = stdx::mp::filtered_index_c<list_t, is_even, 4>;
static_assert(std::same_as<index_of_0, boost::mp11::mp_size_t<0>>);
static_assert(std::same_as<index_of_2, boost::mp11::mp_size_t<1>>);
static_assert(std::same_as<index_of_4, boost::mp11::mp_size_t<2>>);

// filtered items don't exist in the list -- by boost convention, the index
// returned is equal to the (filtered) list size
using index_of_1 = stdx::mp::filtered_index_c<list_t, is_even, 1>;
using index_of_3 = stdx::mp::filtered_index_c<list_t, is_even, 3>;
static_assert(std::same_as<index_of_1, boost::mp11::mp_size_t<3>>);
static_assert(std::same_as<index_of_3, boost::mp11::mp_size_t<3>>);
----
38 changes: 38 additions & 0 deletions include/stdx/meta.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
#pragma once

#include <boost/mp11/algorithm.hpp>
#include <boost/mp11/function.hpp>
#include <boost/mp11/integral.hpp>

#include <limits>

namespace stdx {
inline namespace v1 {
namespace mp {
namespace detail {
template <template <typename...> typename P> struct mask_bit_q {
template <typename T>
using fn = boost::mp11::mp_size_t<P<T>::value ? 1 : 0>;
};

template <typename L, template <typename...> typename P>
using list_mask_t = boost::mp11::mp_transform_q<mask_bit_q<P>, L>;

template <typename L, template <typename...> typename P>
using list_mask_scan_t = boost::mp11::mp_partial_sum<
list_mask_t<L, P>,
boost::mp11::mp_size_t<std::numeric_limits<std::size_t>::max()>,
boost::mp11::mp_plus>;
} // namespace detail

template <typename L, template <typename...> typename P, typename I>
using filtered_index =
boost::mp11::mp_if<P<boost::mp11::mp_at<L, I>>,
boost::mp11::mp_at<detail::list_mask_scan_t<L, P>, I>,
boost::mp11::mp_size<boost::mp11::mp_copy_if<L, P>>>;

template <typename L, template <typename...> typename P, std::size_t I>
using filtered_index_c = filtered_index<L, P, boost::mp11::mp_size_t<I>>;
} // namespace mp
} // namespace v1
} // namespace stdx
1 change: 1 addition & 0 deletions test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ add_tests(
intrusive_list_properties
iterator
latched
meta
numeric
optional
overload
Expand Down
44 changes: 44 additions & 0 deletions test/meta.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
#include <stdx/meta.hpp>

#include <boost/mp11/algorithm.hpp>
#include <catch2/catch_test_macros.hpp>

namespace {
using list_t = boost::mp11::mp_iota_c<5>;
template <typename T> using is_even = std::bool_constant<T::value % 2 == 0>;
} // namespace

TEST_CASE("filtered_index (items that pass the predicate)", "[meta]") {
STATIC_CHECK(
std::same_as<stdx::mp::filtered_index<list_t, is_even,
boost::mp11::mp_size_t<0>>,
boost::mp11::mp_size_t<0>>);
STATIC_CHECK(
std::same_as<stdx::mp::filtered_index<list_t, is_even,
boost::mp11::mp_size_t<2>>,
boost::mp11::mp_size_t<1>>);
STATIC_CHECK(
std::same_as<stdx::mp::filtered_index<list_t, is_even,
boost::mp11::mp_size_t<4>>,
boost::mp11::mp_size_t<2>>);
}

TEST_CASE("filtered_index (items that fail the predicate)", "[meta]") {
STATIC_CHECK(
std::same_as<stdx::mp::filtered_index<list_t, is_even,
boost::mp11::mp_size_t<1>>,
boost::mp11::mp_size_t<3>>);
STATIC_CHECK(
std::same_as<stdx::mp::filtered_index<list_t, is_even,
boost::mp11::mp_size_t<3>>,
boost::mp11::mp_size_t<3>>);
}

TEST_CASE("filtered_index_c", "[meta]") {
STATIC_CHECK(std::same_as<stdx::mp::filtered_index_c<list_t, is_even, 0>,
boost::mp11::mp_size_t<0>>);
STATIC_CHECK(std::same_as<stdx::mp::filtered_index_c<list_t, is_even, 2>,
boost::mp11::mp_size_t<1>>);
STATIC_CHECK(std::same_as<stdx::mp::filtered_index_c<list_t, is_even, 4>,
boost::mp11::mp_size_t<2>>);
}