1+ #pragma once
2+ #include <algorithm>
3+ #include <meta>
4+ #include <string_view>
5+ #include <vector>
6+ #include <tuple>
7+ #include <type_traits>
8+ #include <utility>
9+ #include <ranges>
10+ #include <cstddef>
11+
12+ #include <rsl/string_constant>
13+ #include <rsl/_impl/parser.hpp>
14+ #include <rsl/_impl/member_cache.hpp>
15+
16+ namespace rsl {
17+
18+ template <typename Impl>
19+ struct [[nodiscard]] kwargs_t : Impl {
20+ using type = Impl;
21+ static constexpr auto _member_cache = [:_impl::cache_members(nonstatic_data_members_of(
22+ ^^Impl,
23+ std::meta::access_context::unchecked())):];
24+ };
25+
26+ template <typename T>
27+ concept is_kwargs = has_template_arguments(^^T) && template_of(^^T) == ^^kwargs_t;
28+
29+ namespace kwargs {
30+ struct NameParser : _impl::Parser {
31+ using _impl::Parser::Parser;
32+
33+ std::vector<std::string_view> names;
34+
35+ constexpr bool parse() {
36+ cursor = 0;
37+
38+ while (is_valid()) {
39+ skip_whitespace();
40+
41+ if (current() == '&') {
42+ // might be captured by reference
43+ ++cursor;
44+ skip_whitespace();
45+ }
46+
47+ if (current() == '.') {
48+ // pack captured, reject
49+ return false;
50+ }
51+
52+ std::size_t start = cursor;
53+
54+ // find '=', ',' or whitespace
55+ skip_to('=', ',', ' ', '\n', '\r', '\t');
56+ if (cursor - start == 0) {
57+ // default capture or invalid name
58+ return false;
59+ }
60+
61+ auto name = data.substr(start, cursor - start);
62+ if (name == "this" || name == "*this") {
63+ // this captured, reject
64+ return false;
65+ }
66+ names.emplace_back(name);
67+
68+ // skip ahead to next capture
69+ // if the current character is already ',', this will not move the cursor
70+ skip_to(',');
71+ ++cursor;
72+
73+ skip_whitespace();
74+ }
75+ return true;
76+ }
77+ };
78+
79+ template <string_constant Names, typename... Ts>
80+ constexpr auto make(Ts&&... values) {
81+ struct kwargs_impl;
82+ consteval {
83+ std::vector<std::meta::info> types{^^Ts...};
84+ std::vector<std::meta::info> args;
85+
86+ auto parser = NameParser(Names);
87+
88+ // with P3068 parser.parse() could throw to provide better diagnostics at this point
89+ if (!parser.parse()) {
90+ return;
91+ }
92+
93+ // associate every argument with the corresponding name
94+ // retrieved by parsing the capture list
95+
96+ // std::views::zip_transform could also be used for this
97+ for (auto [member, name] : std::views::zip(types, parser.names)) {
98+ args.push_back(data_member_spec(member, {.name = name}));
99+ }
100+ define_aggregate(^^kwargs_impl, args);
101+ };
102+
103+ // ensure injecting the class worked
104+ static_assert(is_type(^^kwargs_impl), std::string{"Invalid keyword arguments `"} + Names + "`");
105+
106+ return kwargs_t<kwargs_impl>{{std::forward<Ts>(values)...}};
107+ }
108+ } // namespace kwargs
109+
110+ template <string_constant Names, typename... Ts>
111+ constexpr auto make_args(Ts&&... values) {
112+ return kwargs::make<Names>(std::forward<Ts>(values)...);
113+ }
114+
115+ template <typename T>
116+ requires is_kwargs<std::remove_cvref_t<T>>
117+ consteval bool has_arg(std::string_view name) {
118+ return std::remove_cvref_t<T>::_member_cache.has_member(name);
119+ }
120+
121+ // get
122+
123+ template <std::size_t Idx, typename T>
124+ requires is_kwargs<std::remove_cvref_t<T>>
125+ constexpr auto get(T&& kwargs) noexcept {
126+ static_assert(std::remove_cvref_t<T>::_member_cache.count > Idx);
127+ return std::remove_cvref_t<T>::_member_cache.template get<Idx>(std::forward<T>(kwargs));
128+ }
129+
130+ template <string_constant name, typename T>
131+ requires is_kwargs<std::remove_cvref_t<T>>
132+ constexpr auto get(T&& kwargs) {
133+ static_assert(has_arg<T>(name), "Keyword argument `" + std::string(name) + "` not found.");
134+
135+ static constexpr auto Idx = std::remove_cvref_t<T>::_member_cache.get_index_of(name);
136+ return get<Idx>(std::forward<T>(kwargs));
137+ }
138+
139+ // get_or
140+ template <std::size_t Idx, typename T, typename R>
141+ requires is_kwargs<std::remove_cvref_t<T>>
142+ constexpr auto get_or(T&& kwargs, R default_) noexcept {
143+ using kwarg_tuple = std::remove_cvref_t<T>;
144+ if constexpr (kwarg_tuple::_member_cache.count > Idx) {
145+ return get<Idx>(std::forward<T>(kwargs));
146+ } else {
147+ return default_;
148+ }
149+ }
150+
151+ template <string_constant name, typename T, typename R>
152+ requires is_kwargs<std::remove_cvref_t<T>>
153+ constexpr auto get_or(T&& kwargs, R default_) {
154+ using kwarg_tuple = std::remove_cvref_t<T>;
155+ if constexpr (kwarg_tuple::_member_cache.count >
156+ std::remove_cvref_t<T>::_member_cache.get_index_of(name)) {
157+ return get<name>(std::forward<T>(kwargs));
158+ } else {
159+ return default_;
160+ }
161+ }
162+ } // namespace rsl
163+
164+ template <typename T>
165+ struct std::tuple_size<rsl::kwargs_t<T>>
166+ : public integral_constant<size_t, rsl::kwargs_t<std::remove_cvref_t<T>>::_member_cache.count> {
167+ };
168+
169+ template <std::size_t Idx, typename T>
170+ struct std::tuple_element<Idx, rsl::kwargs_t<T>> {
171+ using type = [:rsl::kwargs_t<std::remove_cvref_t<T>>::_member_cache.types[Idx]:];
172+ };
173+
174+ #define RSL_KWARGS(...) \
175+ [__VA_ARGS__]<typename T>(this T _impl_this) { \
176+ constexpr static auto _impl_captures = \
177+ define_static_array(nonstatic_data_members_of(^^T, std::meta::access_context::current())); \
178+ return [&]<std::size_t... Idx>(std::index_sequence<Idx...>) { \
179+ return rsl::kwargs::make<#__VA_ARGS__>( \
180+ std::forward<decltype(_impl_this.[:_impl_captures[Idx]:])>( \
181+ _impl_this.[:_impl_captures[Idx]:])...); \
182+ }(std::make_index_sequence<_impl_captures.size()>()); \
183+ }()
184+
185+ #define $args(...) RSL_KWARGS(__VA_ARGS__)
0 commit comments