Skip to content

Commit d1c55e3

Browse files
committed
🎨 Make function_traits available in freestanding
Problem: - `function_traits` uses `std::function` which is not available in freestanding implementations. Solution: - Rewrite `function_traits` to avoid relying on `std::function`. Note: - We do not deal with `noexcept`-qualified functions here.
1 parent 9a4f0a9 commit d1c55e3

File tree

6 files changed

+291
-24
lines changed

6 files changed

+291
-24
lines changed

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ build/
33
/venv
44
/.vscode
55
/.idea
6-
/.cache
6+
.cache/
77
/.DS_Store
88
.clang-format
99
.clang-tidy

docs/for_each_n_args.adoc

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,6 @@ https://github.com/intel/cpp-std-extensions/blob/main/include/stdx/for_each_n_ar
55
provides a method for calling a function (or other callable) with batches of
66
arguments from a parameter pack.
77

8-
IMPORTANT: `for_each_n_args` is not yet available on freestanding implementations.
9-
108
Examples:
119
[source,cpp]
1210
----

docs/function_traits.adoc

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,6 @@ https://github.com/intel/cpp-std-extensions/blob/main/include/stdx/function_trai
55
contains type traits for introspecting function signatures. It works with
66
functions, lambda expressions, and classes with `operator()`.
77

8-
IMPORTANT: Function traits are not yet available on freestanding implementations.
9-
108
Examples:
119
[source,cpp]
1210
----

include/stdx/function_traits.hpp

Lines changed: 113 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -5,32 +5,136 @@
55
#include <boost/mp11/algorithm.hpp>
66
#include <boost/mp11/utility.hpp>
77

8-
#include <functional>
98
#include <type_traits>
109
#include <utility>
1110

1211
namespace stdx {
1312
inline namespace v1 {
1413
namespace detail {
15-
template <typename...> struct function_traits;
14+
template <auto> struct any_type {
15+
// NOLINTNEXTLINE(google-explicit-constructor)
16+
template <typename T> operator T();
17+
};
1618

17-
template <typename R, typename... Args>
18-
struct function_traits<std::function<R(Args...)>> {
19+
template <typename F> struct function_traits;
20+
21+
template <typename R, typename... Args> struct function_traits<R(Args...)> {
1922
using return_type = R;
2023

2124
template <template <typename...> typename List> using args = List<Args...>;
2225
template <template <typename...> typename List>
2326
using decayed_args = List<std::decay_t<Args>...>;
2427
using arity = std::integral_constant<std::size_t, sizeof...(Args)>;
28+
using obj_t = void;
2529

2630
template <auto N> using nth_arg = nth_t<N, Args...>;
2731
template <auto N> using decayed_nth_arg = std::decay_t<nth_arg<N>>;
32+
33+
template <std::size_t... Is>
34+
constexpr static auto invoke(std::index_sequence<Is...>)
35+
-> std::invoke_result_t<R(Args...), any_type<Is>...>;
36+
};
37+
38+
template <typename R, typename... Args>
39+
struct function_traits<R (*)(Args...)> : function_traits<R(Args...)> {};
40+
41+
template <typename R, typename... Args>
42+
struct function_traits<R (*const)(Args...)> : function_traits<R(Args...)> {};
43+
44+
template <typename R, typename... Args>
45+
struct function_traits<R (*volatile)(Args...)> : function_traits<R(Args...)> {};
46+
47+
template <typename R, typename... Args>
48+
struct function_traits<R (*const volatile)(Args...)>
49+
: function_traits<R(Args...)> {};
50+
51+
template <typename C, typename R, typename... Args>
52+
struct function_traits<R (C::*)(Args...)> : function_traits<R(Args...)> {
53+
using obj_t = C;
54+
};
55+
56+
template <typename C, typename R, typename... Args>
57+
struct function_traits<R (C::*)(Args...) &> : function_traits<R(Args...)> {
58+
using obj_t = C &;
59+
};
60+
61+
template <typename C, typename R, typename... Args>
62+
struct function_traits<R (C::*)(Args...) const &>
63+
: function_traits<R(Args...)> {
64+
using obj_t = C const &;
65+
};
66+
67+
template <typename C, typename R, typename... Args>
68+
struct function_traits<R (C::*)(Args...) volatile &>
69+
: function_traits<R(Args...)> {
70+
using obj_t = C volatile &;
71+
};
72+
73+
template <typename C, typename R, typename... Args>
74+
struct function_traits<R (C::*)(Args...) const volatile &>
75+
: function_traits<R(Args...)> {
76+
using obj_t = C const volatile &;
77+
};
78+
79+
template <typename C, typename R, typename... Args>
80+
struct function_traits<R (C::*)(Args...) &&> : function_traits<R(Args...)> {
81+
using obj_t = C &&;
82+
};
83+
84+
template <typename C, typename R, typename... Args>
85+
struct function_traits<R (C::*)(Args...) const &&>
86+
: function_traits<R(Args...)> {
87+
using obj_t = C const &&;
88+
};
89+
90+
template <typename C, typename R, typename... Args>
91+
struct function_traits<R (C::*)(Args...) volatile &&>
92+
: function_traits<R(Args...)> {
93+
using obj_t = C volatile &&;
94+
};
95+
96+
template <typename C, typename R, typename... Args>
97+
struct function_traits<R (C::*)(Args...) const volatile &&>
98+
: function_traits<R(Args...)> {
99+
using obj_t = C const volatile &&;
100+
};
101+
102+
template <typename C, typename R, typename... Args>
103+
struct function_traits<R (C::*)(Args...) const> : function_traits<R(Args...)> {
104+
using obj_t = C const;
105+
};
106+
107+
template <typename C, typename R, typename... Args>
108+
struct function_traits<R (C::*)(Args...) volatile>
109+
: function_traits<R(Args...)> {
110+
using obj_t = C volatile;
28111
};
29-
} // namespace detail
30112

113+
template <typename C, typename R, typename... Args>
114+
struct function_traits<R (C::*)(Args...) const volatile>
115+
: function_traits<R(Args...)> {
116+
using obj_t = C const volatile;
117+
};
118+
119+
template <typename F, typename = void> struct detect_call_operator {
120+
template <std::size_t... Is>
121+
constexpr static auto invoke(std::index_sequence<Is...>) ->
122+
typename boost::mp11::mp_cond<
123+
boost::mp11::mp_valid<std::invoke_result_t, F &&, any_type<Is>...>,
124+
boost::mp11::mp_defer<std::invoke_result_t, F &&, any_type<Is>...>,
125+
boost::mp11::mp_valid<std::invoke_result_t, F &, any_type<Is>...>,
126+
boost::mp11::mp_defer<std::invoke_result_t, F &,
127+
any_type<Is>...>>::type;
128+
};
31129
template <typename F>
32-
using function_traits =
33-
detail::function_traits<decltype(std::function{std::declval<F>()})>;
130+
struct detect_call_operator<
131+
F, std::void_t<decltype(&remove_cvref_t<F>::operator())>>
132+
: function_traits<decltype(&remove_cvref_t<F>::operator())> {};
133+
134+
template <typename F> struct function_traits : detect_call_operator<F> {};
135+
} // namespace detail
136+
137+
template <typename F> using function_traits = detail::function_traits<F>;
34138

35139
template <typename F> using return_t = typename function_traits<F>::return_type;
36140
template <typename F, template <typename...> typename List>
@@ -39,6 +143,7 @@ template <typename F, template <typename...> typename List>
39143
using decayed_args_t = typename function_traits<F>::template decayed_args<List>;
40144
template <typename F>
41145
using nongeneric_arity_t = typename function_traits<F>::arity;
146+
template <typename F> using obj_arg_t = typename function_traits<F>::obj_t;
42147

43148
template <typename F, auto N>
44149
using nth_arg_t = typename function_traits<F>::template nth_arg<N>;
@@ -47,18 +152,9 @@ using decayed_nth_arg_t =
47152
typename function_traits<F>::template decayed_nth_arg<N>;
48153

49154
namespace detail {
50-
template <auto> struct any_type {
51-
// NOLINTNEXTLINE(google-explicit-constructor)
52-
template <typename T> operator T();
53-
};
54-
55-
template <typename F, std::size_t... Is>
56-
constexpr auto try_invoke_impl(std::index_sequence<Is...>)
57-
-> std::invoke_result_t<F, any_type<Is>...>;
58-
59155
template <typename F, typename N>
60156
using try_invoke =
61-
decltype(try_invoke_impl<F>(std::make_index_sequence<N::value>{}));
157+
decltype(function_traits<F>::invoke(std::make_index_sequence<N::value>{}));
62158

63159
template <typename F, typename N>
64160
using has_arg_count = boost::mp11::mp_valid<try_invoke, F, N>;

test/function_traits.cpp

Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,33 @@ TEST_CASE("function return type", "[function_traits]") {
2424
STATIC_REQUIRE(std::is_same_v<stdx::return_t<decltype(func_one_arg)>, int>);
2525
}
2626

27+
TEST_CASE("function pointer return type", "[function_traits]") {
28+
STATIC_REQUIRE(
29+
std::is_void_v<typename stdx::function_traits<
30+
std::add_pointer_t<decltype(func_no_args)>>::return_type>);
31+
STATIC_REQUIRE(std::is_void_v<
32+
stdx::return_t<std::add_pointer_t<decltype(func_no_args)>>>);
33+
34+
STATIC_REQUIRE(
35+
std::is_same_v<typename stdx::function_traits<std::add_pointer_t<
36+
decltype(func_one_arg)>>::return_type,
37+
int>);
38+
STATIC_REQUIRE(
39+
std::is_same_v<
40+
stdx::return_t<std::add_pointer_t<decltype(func_one_arg)>>, int>);
41+
}
42+
43+
TEST_CASE("qualified function pointer return type", "[function_traits]") {
44+
STATIC_REQUIRE(
45+
std::is_void_v<
46+
stdx::return_t<std::add_pointer_t<decltype(func_no_args)> const>>);
47+
STATIC_REQUIRE(std::is_void_v<stdx::return_t<
48+
std::add_pointer_t<decltype(func_no_args)> volatile>>);
49+
STATIC_REQUIRE(
50+
std::is_void_v<stdx::return_t<
51+
std::add_pointer_t<decltype(func_no_args)> const volatile>>);
52+
}
53+
2754
TEST_CASE("lambda return type", "[function_traits]") {
2855
[[maybe_unused]] auto const x = []() {};
2956
[[maybe_unused]] auto const y = []() mutable {};
@@ -197,3 +224,153 @@ TEST_CASE("SFINAE friendly", "[function_traits]") {
197224
call_f(f2, stdx::priority<1>);
198225
CHECK(called_2 == 1);
199226
}
227+
228+
namespace {
229+
struct S {
230+
[[maybe_unused]] auto f() -> void {}
231+
};
232+
struct S_C {
233+
[[maybe_unused]] auto f() const -> void {}
234+
};
235+
struct S_V {
236+
[[maybe_unused]] auto f() volatile -> void {}
237+
};
238+
struct S_CV {
239+
[[maybe_unused]] auto f() const volatile -> void {}
240+
};
241+
242+
struct S_LV {
243+
[[maybe_unused]] auto f() & -> void {}
244+
};
245+
struct S_RV {
246+
[[maybe_unused]] auto f() && -> void {}
247+
};
248+
struct S_CLV {
249+
[[maybe_unused]] auto f() const & -> void {}
250+
};
251+
struct S_CRV {
252+
[[maybe_unused]] auto f() const && -> void {}
253+
};
254+
255+
struct S_VLV {
256+
[[maybe_unused]] auto f() volatile & -> void {}
257+
};
258+
struct S_VRV {
259+
[[maybe_unused]] auto f() volatile && -> void {}
260+
};
261+
262+
struct S_CVLV {
263+
[[maybe_unused]] auto f() const volatile & -> void {}
264+
};
265+
struct S_CVRV {
266+
[[maybe_unused]] auto f() const volatile && -> void {}
267+
};
268+
} // namespace
269+
270+
TEST_CASE("member function return type", "[function_traits]") {
271+
STATIC_REQUIRE(std::is_void_v<stdx::return_t<decltype(&S::f)>>);
272+
STATIC_REQUIRE(std::is_void_v<stdx::return_t<decltype(&S_C::f)>>);
273+
STATIC_REQUIRE(std::is_void_v<stdx::return_t<decltype(&S_V::f)>>);
274+
STATIC_REQUIRE(std::is_void_v<stdx::return_t<decltype(&S_CV::f)>>);
275+
}
276+
277+
TEST_CASE("member function arity", "[function_traits]") {
278+
STATIC_REQUIRE(stdx::arity_v<decltype(&S::f)> == 0);
279+
STATIC_REQUIRE(stdx::arity_v<decltype(&S_C::f)> == 0);
280+
STATIC_REQUIRE(stdx::arity_v<decltype(&S_V::f)> == 0);
281+
STATIC_REQUIRE(stdx::arity_v<decltype(&S_CV::f)> == 0);
282+
}
283+
284+
TEST_CASE("object argument types (const/volatile)", "[function_traits]") {
285+
STATIC_REQUIRE(std::is_void_v<stdx::obj_arg_t<decltype(&func_no_args)>>);
286+
STATIC_REQUIRE(std::is_same_v<S, stdx::obj_arg_t<decltype(&S::f)>>);
287+
STATIC_REQUIRE(
288+
std::is_same_v<S_C const, stdx::obj_arg_t<decltype(&S_C::f)>>);
289+
STATIC_REQUIRE(
290+
std::is_same_v<S_V volatile, stdx::obj_arg_t<decltype(&S_V::f)>>);
291+
STATIC_REQUIRE(std::is_same_v<S_CV const volatile,
292+
stdx::obj_arg_t<decltype(&S_CV::f)>>);
293+
}
294+
295+
TEST_CASE("object argument types (value categories)", "[function_traits]") {
296+
STATIC_REQUIRE(std::is_same_v<S_LV &, stdx::obj_arg_t<decltype(&S_LV::f)>>);
297+
STATIC_REQUIRE(
298+
std::is_same_v<S_RV &&, stdx::obj_arg_t<decltype(&S_RV::f)>>);
299+
STATIC_REQUIRE(
300+
std::is_same_v<S_CLV const &, stdx::obj_arg_t<decltype(&S_CLV::f)>>);
301+
STATIC_REQUIRE(
302+
std::is_same_v<S_CRV const &&, stdx::obj_arg_t<decltype(&S_CRV::f)>>);
303+
304+
STATIC_REQUIRE(
305+
std::is_same_v<S_VLV volatile &, stdx::obj_arg_t<decltype(&S_VLV::f)>>);
306+
STATIC_REQUIRE(std::is_same_v<S_VRV volatile &&,
307+
stdx::obj_arg_t<decltype(&S_VRV::f)>>);
308+
STATIC_REQUIRE(std::is_same_v<S_CVLV const volatile &,
309+
stdx::obj_arg_t<decltype(&S_CVLV::f)>>);
310+
STATIC_REQUIRE(std::is_same_v<S_CVRV const volatile &&,
311+
stdx::obj_arg_t<decltype(&S_CVRV::f)>>);
312+
}
313+
314+
TEST_CASE("object argument type (lambda)", "[function_traits]") {
315+
[[maybe_unused]] auto const x = [](int) {};
316+
STATIC_REQUIRE(std::is_same_v<decltype(x), stdx::obj_arg_t<decltype(x)>>);
317+
}
318+
319+
namespace {
320+
struct TS {
321+
template <typename T> auto operator()(T) -> void {}
322+
};
323+
struct TS_C {
324+
template <typename T> auto operator()(T) const -> void {}
325+
};
326+
struct TS_V {
327+
template <typename T> auto operator()(T) volatile -> void {}
328+
};
329+
struct TS_CV {
330+
template <typename T> auto operator()(T) const volatile -> void {}
331+
};
332+
333+
struct TS_LV {
334+
template <typename T> auto operator()(T) & -> void {}
335+
};
336+
struct TS_RV {
337+
template <typename T> auto operator()(T) && -> void {}
338+
};
339+
struct TS_CLV {
340+
template <typename T> auto operator()(T) const & -> void {}
341+
};
342+
struct TS_CRV {
343+
template <typename T> auto operator()(T) const && -> void {}
344+
};
345+
346+
struct TS_VLV {
347+
template <typename T> auto operator()(T) volatile & -> void {}
348+
};
349+
struct TS_VRV {
350+
template <typename T> auto operator()(T) volatile && -> void {}
351+
};
352+
353+
struct TS_CVLV {
354+
template <typename T> auto operator()(T) const volatile & -> void {}
355+
};
356+
struct TS_CVRV {
357+
template <typename T> auto operator()(T) const volatile && -> void {}
358+
};
359+
} // namespace
360+
361+
TEST_CASE("member function template arity", "[function_traits]") {
362+
STATIC_REQUIRE(stdx::arity_v<TS> == 1);
363+
STATIC_REQUIRE(stdx::arity_v<TS_C> == 1);
364+
STATIC_REQUIRE(stdx::arity_v<TS_V> == 1);
365+
STATIC_REQUIRE(stdx::arity_v<TS_CV> == 1);
366+
367+
STATIC_REQUIRE(stdx::arity_v<TS_LV> == 1);
368+
STATIC_REQUIRE(stdx::arity_v<TS_RV> == 1);
369+
STATIC_REQUIRE(stdx::arity_v<TS_CLV> == 1);
370+
STATIC_REQUIRE(stdx::arity_v<TS_CRV> == 1);
371+
372+
STATIC_REQUIRE(stdx::arity_v<TS_VLV> == 1);
373+
STATIC_REQUIRE(stdx::arity_v<TS_VRV> == 1);
374+
STATIC_REQUIRE(stdx::arity_v<TS_CVLV> == 1);
375+
STATIC_REQUIRE(stdx::arity_v<TS_CVRV> == 1);
376+
}

usage_test/main.cpp

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,8 @@
1919
#include <stdx/cx_set.hpp>
2020
#include <stdx/cx_vector.hpp>
2121
#include <stdx/env.hpp>
22-
#ifndef SIMULATE_FREESTANDING
2322
#include <stdx/for_each_n_args.hpp>
2423
#include <stdx/function_traits.hpp>
25-
#endif
2624
#include <stdx/functional.hpp>
2725
#include <stdx/intrusive_forward_list.hpp>
2826
#include <stdx/intrusive_list.hpp>

0 commit comments

Comments
 (0)