Skip to content

Commit

Permalink
Merge pull request #11 from elbeno/for-each-n-args
Browse files Browse the repository at this point in the history
For each n args
  • Loading branch information
elbeno authored Sep 15, 2023
2 parents 01b5d27 + a96fbfb commit fd4da3b
Show file tree
Hide file tree
Showing 12 changed files with 196 additions and 3 deletions.
29 changes: 29 additions & 0 deletions docs/for_each_n_args.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@

== `for_each_n_args.hpp`

https://github.com/intel/cpp-std-extensions/blob/main/include/stdx/for_each_n_args.hpp[`for_each_n_args.hpp`]
provides a method for calling a function (or other callable) with batches of
arguments from a parameter pack.

Examples:
[source,cpp]
----
auto f(int x, int y) -> void { /* do something with x and y */ }
stdx::for_each_n_args(f, 1, 2, 3, 4); // this calls f(1, 2) and f(3, 4)
----

The number of arguments passed to `for_each_n_args` must be a multiple of the
argument "batch size" - which by default is the arity of the passed function.

Sometimes, the passed callable is a generic function where the arity cannot be
automatically determined, or sometimes it may be a function with default
arguments which we want to use. In that case it is possible to override the
default batch size:
[source,cpp]
----
auto f(auto x, auto y) -> void { /* do something with x and y */ }
stdx::for_each_n_args<2>(f, 1, 2, 3, 4); // this calls f(1, 2) and f(3, 4)
auto g(int x, int y, int z = 42) -> void { /* do something with x, y and z */ }
stdx::for_each_n_args<2>(g, 1, 2, 3, 4); // this calls g(1, 2, 42) and g(3, 4, 42)
----
9 changes: 8 additions & 1 deletion docs/function_traits.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,20 @@ using l_args = stdx::args_t<decltype(l), std::tuple>; // std::tuple<int>
`stdx::args_t` returns a list of the function arguments. `std::decayed_args_t`
returns the same list, but with `std::decay_t` applied to each element. This is
useful for example when you need to copy and store a tuple of the arguments.

[source,cpp]
----
auto f(int&, std::string&) -> void {}
using f_args = stdx::decayed_args_t<decltype(f), std::tuple>; // std::tuple<int, std::string>
----

`stdx::arity_t` returns the arity of a function (as a `std::integral_constant`).
[source,cpp]
----
auto f(int&, std::string&) -> void {}
using f_arity = stdx::arity_t<decltype(f)>; // std::integral_constant<std::size_t, 2>
----


NOTE: Function traits work on functions (and function objects): not function
templates or overload sets. For instance therefore, they will not work on generic
lambda expressions.
1 change: 1 addition & 0 deletions docs/index.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ include::cx_multimap.adoc[]
include::cx_queue.adoc[]
include::cx_set.adoc[]
include::cx_vector.adoc[]
include::for_each_n_args.adoc[]
include::function_traits.adoc[]
include::functional.adoc[]
include::intrusive_list.adoc[]
Expand Down
1 change: 1 addition & 0 deletions docs/type_traits.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ contains a few things from the standard:
(implemented with fewer template instantiations than a typical standard
implementation)
* https://en.cppreference.com/w/cpp/types/is_function[`is_function_v`] (implemented with Walter Brown's method)
* https://en.cppreference.com/w/cpp/types/is_constant_evaluated[`is_constant_evaluated`] (from C++20)

It also contains `always_false_v`, a variable template that can be instantiated
with any number of type arguments and always evaluates to false at compile-time.
Expand Down
64 changes: 64 additions & 0 deletions include/stdx/for_each_n_args.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
#pragma once

#include <stdx/function_traits.hpp>

#if __cplusplus >= 202002L
#include <stdx/tuple.hpp>
#define TUPLE_T stdx::tuple
#define TUPLE_GET stdx::get
#else
#include <tuple>
#define TUPLE_T std::tuple
#define TUPLE_GET std::get
#endif

#include <cstddef>
#include <functional>
#include <utility>

namespace stdx {
inline namespace v1 {

namespace detail {
template <typename, typename> struct for_each_n_args;

template <std::size_t... Rows, std::size_t... Columns>
struct for_each_n_args<std::index_sequence<Rows...>,
std::index_sequence<Columns...>> {
template <typename F, typename T> static auto apply(F &&f, T &&t) -> void {
(exec<Rows * sizeof...(Columns)>(f, std::forward<T>(t)), ...);
}

private:
template <std::size_t RowIndex, typename F, typename T>
static auto exec(F &&f, T &&t) -> void {
std::invoke(f, TUPLE_GET<RowIndex + Columns>(std::forward<T>(t))...);
}
};
} // namespace detail

template <std::size_t N = 0, typename F, typename... Args>
void for_each_n_args(F &&f, Args &&...args) {
constexpr auto batch_size = [] {
if constexpr (N == 0) {
return arity_t<F>::value;
} else {
return N;
}
}();
static_assert(sizeof...(Args) % batch_size == 0,
"for_each_n_args: number of args must be a multiple of the "
"given N (or function arity)");

using tuple_t = TUPLE_T<Args &&...>;
detail::for_each_n_args<
std::make_index_sequence<sizeof...(Args) / batch_size>,
std::make_index_sequence<batch_size>>::apply(std::forward<F>(f),
tuple_t{std::forward<Args>(
args)...});
}
} // namespace v1
} // namespace stdx

#undef TUPLE_T
#undef TUPLE_GET
2 changes: 2 additions & 0 deletions include/stdx/function_traits.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ struct function_traits<std::function<R(Args...)>> {
template <template <typename...> typename List> using args = List<Args...>;
template <template <typename...> typename List>
using decayed_args = List<std::decay_t<Args>...>;
using arity = std::integral_constant<std::size_t, sizeof...(Args)>;
};
} // namespace detail

Expand All @@ -28,6 +29,7 @@ template <typename F, template <typename...> typename List>
using args_t = typename function_traits<F>::template args<List>;
template <typename F, template <typename...> typename List>
using decayed_args_t = typename function_traits<F>::template decayed_args<List>;
template <typename F> using arity_t = typename function_traits<F>::arity;

} // namespace v1
} // namespace stdx
3 changes: 3 additions & 0 deletions include/stdx/type_traits.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -56,5 +56,8 @@ constexpr bool is_function_object_v = detail::is_func_obj<T>;
template <typename T>
constexpr bool is_callable_v = is_function_v<T> or is_function_object_v<T>;

constexpr auto is_constant_evaluated() noexcept -> bool {
return __builtin_is_constant_evaluated();
}
} // namespace v1
} // namespace stdx
2 changes: 2 additions & 0 deletions test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,10 @@ add_tests(
cx_set
cx_vector
default_panic
for_each_n_args
function_traits
intrusive_list
is_constant_evaluated
overload
panic
priority
Expand Down
6 changes: 4 additions & 2 deletions test/detail/tuple_types.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@ struct move_only {
constexpr move_only(move_only &&) = default;
constexpr auto operator=(move_only &&) noexcept -> move_only & = default;

friend constexpr auto operator==(move_only const &, move_only const &)
-> bool = default;
friend constexpr auto operator==(move_only const &lhs, move_only const &rhs)
-> bool {
return lhs.value == rhs.value;
}

int value{};
};
Expand Down
40 changes: 40 additions & 0 deletions test/for_each_n_args.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
#include "detail/tuple_types.hpp"

#include <stdx/for_each_n_args.hpp>

#include <catch2/catch_test_macros.hpp>

TEST_CASE("default to unary function", "[for_each_n_args]") {
auto sum = 0;
auto f = [&](int x) { sum += x; };
stdx::for_each_n_args(f, 1, 2, 3);
CHECK(sum == 6);
}

TEST_CASE("binary function", "[for_each_n_args]") {
auto sum = 0;
auto f = [&](int x, int y) { sum += x * y; };
stdx::for_each_n_args<2>(f, 2, 3, 3, 4);
CHECK(sum == 18);
}

TEST_CASE("generic function", "[for_each_n_args]") {
auto sum = 0;
auto f = [&](auto x, auto y) { sum += x * y; };
stdx::for_each_n_args<2>(f, 2, 3, 3, 4);
CHECK(sum == 18);
}

TEST_CASE("move-only arguments", "[for_each_n_args]") {
auto sum = 0;
auto f = [&](move_only x) { sum += x.value; };
stdx::for_each_n_args(f, move_only{1}, move_only{2}, move_only{3});
CHECK(sum == 6);
}

TEST_CASE("default arguments", "[for_each_n_args]") {
auto sum = 0;
auto f = [&](int x, int y, int z = 42) { sum += x * y + z; };
stdx::for_each_n_args<2>(f, 2, 3, 3, 4);
CHECK(sum == 42 + 42 + 18);
}
19 changes: 19 additions & 0 deletions test/function_traits.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,25 @@ TEST_CASE("lambda decayed args", "[function_traits]") {
std::tuple<int>>);
}

TEST_CASE("function arity", "[function_traits]") {
static_assert(stdx::function_traits<decltype(func_no_args)>::arity::value ==
0u);
static_assert(stdx::function_traits<decltype(func_one_arg)>::arity::value ==
1u);
static_assert(stdx::arity_t<decltype(func_no_args)>::value == 0u);
static_assert(stdx::arity_t<decltype(func_one_arg)>::value == 1u);
}

TEST_CASE("lambda arity", "[function_traits]") {
[[maybe_unused]] auto const x = []() {};
[[maybe_unused]] auto const y = [](int) {};

static_assert(stdx::function_traits<decltype(x)>::arity::value == 0u);
static_assert(stdx::function_traits<decltype(y)>::arity::value == 1u);
static_assert(stdx::arity_t<decltype(x)>::value == 0u);
static_assert(stdx::arity_t<decltype(y)>::value == 1u);
}

namespace {
bool called_1{};
bool called_2{};
Expand Down
23 changes: 23 additions & 0 deletions test/is_constant_evaluated.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
#include <stdx/type_traits.hpp>

#include <catch2/catch_test_macros.hpp>

namespace {
constexpr auto f() -> int {
if (stdx::is_constant_evaluated()) {
return 42;
} else {
return 0;
}
}
} // namespace

TEST_CASE("constexpr context", "[is_constant_evaluated]") {
constexpr auto n = f();
CHECK(n == 42);
}

TEST_CASE("runtime context", "[is_constant_evaluated]") {
auto n = f();
CHECK(n == 0);
}

0 comments on commit fd4da3b

Please sign in to comment.