GMP 0.4.0
Generative Metaprogramming library for C++
Loading...
Searching...
No Matches
meta.hpp
Go to the documentation of this file.
1// ___ __ __ ___
2// / __| \/ | _ \ GMP(Generative Metaprogramming)
3// | (_ | |\/| | _/ version 0.3.0
4// \___|_| |_|_| https://github.com/lkimuk/gmp
5//
6// SPDX-FileCopyrightText: 2026 Miles Li <https://www.cppmore.com/>
7// SPDX-License-Identifier: MIT
8//
9// This file is part of the GMP (Generative Metaprogramming) library.
10// Full project source: https://github.com/lkimuk/gmp
11
12#ifndef GMP_META_HPP_
13#define GMP_META_HPP_
14
15#include <array>
16#include <functional>
17#include <optional>
18#include <source_location>
19#include <string_view>
20#include <tuple>
21#include <type_traits>
22#include <utility>
23
24#include <gmp/meta/config.hpp>
26#include <gmp/meta/detail/member_ref.hpp>
27
28namespace gmp {
29
45template<typename E>
47 static constexpr int min = -128;
48 static constexpr int max = 127;
49 // static constexpr bool is_flags = false;
50 // static constexpr bool allow_alias = false;
51};
52
64#define GMP_ENUM_RANGE(Enum, Min, Max) \
65 template<> \
66 struct gmp::enum_traits<Enum> { \
67 static constexpr int min = Min; \
68 static constexpr int max = Max; \
69 }
70
81#define GMP_ENUM_VALUES(Enum, ...) \
82 template<> \
83 struct gmp::enum_traits<Enum> { \
84 static constexpr auto values = std::to_array({__VA_ARGS__}); \
85 }
86
92template<typename E>
93inline constexpr int enum_min_v = enum_traits<E>::min;
94
100template<typename E>
101inline constexpr int enum_max_v = enum_traits<E>::max;
102
105namespace detail {
106
107template<typename E>
108concept has_enum_values =
109 requires {
111 std::tuple_size<std::remove_cvref_t<decltype(enum_traits<E>::values)>>::value;
112 } &&
113 std::same_as<
114 typename std::remove_cvref_t<decltype(enum_traits<E>::values)>::value_type,
115 E
116 >;
117
118template<typename E, fixed_string P, auto V>
119consteval bool is_valid_enum_value() {
120 constexpr auto name = value_name_of<static_cast<E>(V)>();
121 if (name.find(P.data()) != std::string_view::npos) {
122 return true;
123 }
124
125#if GMP_COMPILER_GCC
126 // GCC spells types declared in an anonymous namespace differently in
127 // `source_location::function_name()`: `type_name<E>()` contains
128 // `{anonymous}`, while the enumerator signature uses `<unnamed>`.
129 // Normalize only the default prefix; an explicitly supplied prefix must
130 // retain its documented filtering semantics.
131 constexpr auto default_prefix = type_name<E>() + fixed_string("::");
132 constexpr auto type = type_name_of<E>();
133 constexpr std::string_view anonymous_prefix = "{anonymous}::";
134 constexpr std::string_view gcc_anonymous_prefix = "<unnamed>::";
135 if constexpr (P.to_string_view() == default_prefix.to_string_view() &&
136 type.starts_with(anonymous_prefix)) {
137 constexpr auto visible_type = type.substr(anonymous_prefix.size());
138 constexpr auto type_pos = name.find(visible_type);
139 if constexpr (type_pos >= gcc_anonymous_prefix.size() &&
140 type_pos + visible_type.size() + 1 < name.size()) {
141 constexpr auto namespace_pos = type_pos - gcc_anonymous_prefix.size();
142 if (name.substr(namespace_pos, gcc_anonymous_prefix.size()) ==
144 name[type_pos + visible_type.size()] == ':' &&
145 name[type_pos + visible_type.size() + 1] == ':') {
146 return true;
147 }
148 }
149 }
150#endif
151
152 return false;
153}
154
155template<typename E, fixed_string P, int Min, std::size_t... I>
156consteval auto enum_values_scan(std::index_sequence<I...>) {
157 constexpr std::size_t count = (std::size_t{0} + ... + (is_valid_enum_value<E, P, Min + static_cast<int>(I)>() ? 1u : 0u));
158
159 std::array<E, count> result{};
160 std::size_t index = 0;
161
162 auto append = [&]<int V>() consteval {
163 if constexpr (is_valid_enum_value<E, P, V>()) {
164 result[index++] = static_cast<E>(V);
165 }
166 };
167
168 (append.template operator()<Min + static_cast<int>(I)>(), ...);
169 return result;
170}
171
172} // namespace detail
173
193consteval auto enum_values() {
194 static_assert(std::is_enum_v<E>, "enum_values<E>() requires E to be an enum type");
195
196 if constexpr (detail::has_enum_values<E>) {
198 } else {
199 constexpr int min = enum_min_v<E>;
200 constexpr int max = enum_max_v<E>;
201
202 static_assert(min <= max, "enum_traits<E>::min must be <= max");
203
204 return detail::enum_values_scan<E, P, min>(
205 std::make_index_sequence<static_cast<std::size_t>(max - min + 1)>{}
206 );
207 }
208}
209
239consteval auto enum_count() {
240 static_assert(std::is_enum_v<E>, "enum_count<E>() requires E to be an enum type");
241
242 return enum_values<E, P>().size();
243}
244
272template<auto E, fixed_string P = fixed_string("::")>
273consteval auto enum_name() {
274 constexpr auto name = detail::value_name_of<E>();
275 constexpr auto start = name.rfind(P.data());
276 if constexpr (start != std::string_view::npos) {
277#if GMP_COMPILER_CLANG || GMP_COMPILER_GCC
278 constexpr auto end = name.find_last_of("]");
279#else
280 constexpr auto end = name.find_last_of(">");
281#endif
282 return name.substr(start + P.size(), end - start - P.size());
283 } else {
284 return "<unnamed>";
285 }
286}
287
328template<typename E>
329consteval auto enum_names() {
330 constexpr auto values = enum_values<E>();
331 constexpr std::size_t size = values.size();
332
333 if constexpr (size == 0) {
334 return std::array<std::string_view, 0>{};
335 } else {
336 return [&]<std::size_t... Is>(std::index_sequence<Is...>) {
337 return std::array<std::string_view, size>{
339 };
340 }(std::make_index_sequence<size>{});
341 }
342}
343
354template<typename E>
355consteval auto enum_entries() {
356 constexpr auto values = enum_values<E>();
357 constexpr auto names = enum_names<E>();
358 constexpr std::size_t size = values.size();
359
360 if constexpr (size == 0) {
361 return std::array<std::pair<E, std::string_view>, 0>{};
362 } else {
363 return [&]<std::size_t... Is>(std::index_sequence<Is...>) {
364 return std::array<std::pair<E, std::string_view>, size>{
365 std::pair<E, std::string_view>{ values[Is], names[Is] }...
366 };
367 }(std::make_index_sequence<size>{});
368 }
369}
370
379template<typename E>
380constexpr auto enum_index(E value) -> std::optional<std::size_t> {
381 constexpr auto values = enum_values<E>();
382 for (std::size_t i = 0; i < values.size(); ++i) {
383 if (values[i] == value) {
384 return i;
385 }
386 }
387 return std::nullopt;
388}
389
398template<typename E>
399constexpr auto enum_cast(std::string_view name) -> std::optional<E> {
400 constexpr auto entries = enum_entries<E>();
401 for (const auto& [value, n] : entries) {
402 if (n == name) {
403 return value;
404 }
405 }
406 return std::nullopt;
407}
408
412template<typename E>
413 requires std::is_enum_v<E>
414constexpr auto enum_underlying(E value) noexcept -> std::underlying_type_t<E> {
415 return static_cast<std::underlying_type_t<E>>(value);
416}
417
421template<typename E>
422 requires std::is_enum_v<E>
423constexpr bool enum_contains(E value) {
424 return enum_index(value).has_value();
425}
426
430template<typename E, typename U>
431 requires std::is_enum_v<E> && std::is_integral_v<U>
432constexpr bool enum_contains(U value) {
433 constexpr auto values = enum_values<E>();
434 for (const auto enumerator : values) {
435 if (std::cmp_equal(+enum_underlying(enumerator), +value)) {
436 return true;
437 }
438 }
439 return false;
440}
441
471template<typename T, typename... Args>
472 requires std::is_aggregate_v<std::remove_cvref_t<T>>
473consteval int member_count() {
474 using value_type = std::remove_cvref_t<T>;
475 if constexpr (requires { value_type{Args{}...}; }) {
476 return member_count<value_type, Args..., gmp::any>();
477 } else {
478 return static_cast<int>(sizeof...(Args)) - 1;
479 }
480}
481
485template<typename T>
486inline constexpr auto member_count_v = member_count<T>();
487
495template<typename T>
496concept reflectable =
497 std::is_class_v<std::remove_cvref_t<T>> &&
498 std::is_aggregate_v<std::remove_cvref_t<T>> &&
501
554template<std::size_t I, typename T>
555 requires std::is_aggregate_v<T> &&
558consteval auto member_name() noexcept {
559 constexpr auto name = detail::member_name_of<
560 T,
561 detail::field_getter<I, T>(constant_arg<member_count<T>()>)
562 >();
563 return name;
564}
565
567template<std::size_t I, typename T>
568consteval auto member_name() noexcept {
569 static_assert(std::is_aggregate_v<T>, "member_name() can only be used with aggregate types.");
570 static_assert(I < member_count<T>(), "Index out of bounds in member_name().");
571 static_assert(member_count<T>() <= GMP_MAX_SUPPORTED_FIELDS, "member_name() only supports up to " GMP_STRINGIFY(GMP_MAX_SUPPORTED_FIELDS) " fields.");
572}
631template<typename T>
632consteval auto member_names() {
633 using value_type = std::remove_cvref_t<T>;
634 constexpr auto size = member_count<value_type>();
635 if constexpr (size == 0) {
636 return std::array<std::string_view, 0>{};
637 } else {
638 return []<std::size_t... Is>(std::index_sequence<Is...>) {
639 return (std::array<std::string_view, size> {
641 });
642 }(std::make_index_sequence<size>{});
643 }
644}
645
649template<typename T>
650 requires reflectable<T>
651constexpr auto member_index(std::string_view name) -> std::optional<std::size_t> {
652 constexpr auto names = member_names<std::remove_cvref_t<T>>();
653 for (std::size_t i = 0; i < names.size(); ++i) {
654 if (names[i] == name) {
655 return i;
656 }
657 }
658 return std::nullopt;
659}
660
664template<typename T>
665 requires reflectable<T>
666constexpr bool has_member(std::string_view name) {
667 return member_index<T>(name).has_value();
668}
669
685template<std::size_t I, typename T>
686using member_type_t = std::remove_cvref_t<
687 decltype(*detail::field_getter<I, T>(constant_arg<member_count<T>()>))>;
688
691namespace detail {
692
693template<typename T>
694struct member_type_names_holder {
695 static constexpr std::size_t N = member_count<T>();
696
697 static constexpr auto fixed_names = []<std::size_t... Is>(std::index_sequence<Is...>) {
698 return std::tuple{ pretty_type_name<member_type_t<Is, T>>()()... };
699 }(std::make_index_sequence<N>{});
700
701 static constexpr auto views = []<std::size_t... Is>(std::index_sequence<Is...>) {
702 return std::array<std::string_view, N>{
703 std::get<Is>(fixed_names).to_string_view()...
704 };
705 }(std::make_index_sequence<N>{});
706};
707
708} // namespace detail
709
742template<typename T>
743constexpr auto member_type_names() {
744 return detail::member_type_names_holder<T>::views;
745}
746
759template<std::size_t I, typename T, typename UnqualifiedT = std::remove_cvref_t<T>>
760 requires std::is_aggregate_v<UnqualifiedT>
763decltype(auto) member_ref(T&& value) noexcept {
764 return detail::member_ref<I>(
765 std::forward<T>(value),
767 );
768}
769
771template<std::size_t I, typename T, typename UnqualifiedT = std::remove_cvref_t<T>>
772decltype(auto) member_ref(T&&) noexcept {
773 static_assert(std::is_aggregate_v<UnqualifiedT>, "member_ref() can only be used with aggregate types.");
774 static_assert(I < member_count<UnqualifiedT>(), "Index out of bounds in member_ref().");
775 static_assert(member_count<UnqualifiedT>() <= GMP_MAX_SUPPORTED_FIELDS, "member_ref() only supports up to " GMP_STRINGIFY(GMP_MAX_SUPPORTED_FIELDS) " fields.");
776}
782template<typename T>
783 requires reflectable<T>
784constexpr auto tie_members(T& value) noexcept {
785 constexpr auto size = member_count_v<T>;
786 return [&]<std::size_t... Is>(std::index_sequence<Is...>) noexcept {
787 return std::forward_as_tuple(
788 member_ref<Is>(value)...
789 );
790 }(std::make_index_sequence<size>{});
791}
792
796template<typename T, typename F>
797 requires reflectable<T>
798constexpr decltype(auto) apply_members(T& value, F&& func)
799 noexcept(noexcept(std::apply(
800 std::forward<F>(func),
801 tie_members(value)
802 ))) {
803 return std::apply(
804 std::forward<F>(func),
805 tie_members(value)
806 );
807}
808
809namespace detail {
810
811template<typename T, typename F, std::size_t... Is>
812constexpr void for_each_member_impl(T&& value, F&& func, std::index_sequence<Is...>)
813 noexcept((std::is_nothrow_invocable_v<
814 F&,
815 std::string_view,
816 decltype(gmp::member_ref<Is>(std::forward<T>(value)))
817 > && ...)) {
818 constexpr auto names = member_names<std::remove_cvref_t<T>>();
819 (std::invoke(
820 func,
821 names[Is],
822 gmp::member_ref<Is>(std::forward<T>(value))
823 ), ...);
824}
825
826} // namespace detail
827
840template<typename T, typename F>
841 requires reflectable<T>
842constexpr void for_each_member(T&& value, F&& func)
843 noexcept(noexcept(detail::for_each_member_impl(
844 std::forward<T>(value),
845 std::forward<F>(func),
846 std::make_index_sequence<member_count_v<std::remove_cvref_t<T>>>{}
847 ))) {
848 detail::for_each_member_impl(
849 std::forward<T>(value),
850 std::forward<F>(func),
851 std::make_index_sequence<member_count_v<std::remove_cvref_t<T>>>{}
852 );
853}
854
856template<typename T, typename F>
857void for_each_member(T&&, F&&) noexcept {
858 using value_type = std::remove_cvref_t<T>;
859 static_assert(std::is_aggregate_v<value_type>,
860 "for_each_member() can only be used with aggregate types.");
861 if constexpr (std::is_aggregate_v<value_type>) {
862 static_assert(std::is_class_v<value_type>,
863 "for_each_member() does not support arrays or unions.");
864 static_assert(reflectable<value_type>,
865 "for_each_member() does not support this aggregate type.");
866 }
867}
876template<typename T, typename F>
877 requires reflectable<T>
878constexpr bool visit_member(T&& value, std::string_view name, F&& func) {
879 bool visited = false;
881 std::forward<T>(value),
882 [&](std::string_view current_name, auto&& member) {
883 if (!visited && current_name == name) {
884 std::invoke(
885 func,
886 std::forward<decltype(member)>(member)
887 );
888 visited = true;
889 }
890 }
891 );
892 return visited;
893}
894
897} // namespace gmp
898
899#endif // GMP_META_HPP_
Constraint for aggregate types supported by the reflection helpers.
Definition meta.hpp:496
#define GMP_MAX_SUPPORTED_FIELDS
Definition config.hpp:26
#define GMP_STRINGIFY(x)
Convert a macro argument to a string literal.
Definition macro.hpp:132
constexpr decltype(auto) apply_members(T &value, F &&func) noexcept(noexcept(std::apply(std::forward< F >(func), tie_members(value))))
Invoke a callable with all aggregate members as separate arguments.
Definition meta.hpp:798
constexpr auto tie_members(T &value) noexcept
Return a tuple of references to all members of an aggregate.
Definition meta.hpp:784
consteval int member_count()
Count the number of members in an aggregate type at compile-time.
Definition meta.hpp:473
consteval auto enum_names()
Get all enumerator names of an enumeration type at compile-time.
Definition meta.hpp:329
constexpr bool enum_contains(E value)
Test whether an enumerator is present in the reflected set.
Definition meta.hpp:423
consteval auto enum_count()
Count the number of enumerators in an enumeration type at compile-time.
Definition meta.hpp:239
consteval auto enum_name()
Get the name of an enumerator at compile-time.
Definition meta.hpp:273
constexpr auto member_type_names()
Returns an array of string_view containing the type names of all members of aggregate type T.
Definition meta.hpp:743
constexpr auto member_index(std::string_view name) -> std::optional< std::size_t >
Find a reflected member by name.
Definition meta.hpp:651
constexpr bool visit_member(T &&value, std::string_view name, F &&func)
Visit a member selected by name.
Definition meta.hpp:878
constexpr int enum_max_v
The maximum reflection scan value for enumeration type E.
Definition meta.hpp:101
std::remove_cvref_t< decltype(*detail::field_getter< I, T >(constant_arg< member_count< T >()>))> member_type_t
Alias for the type of the I-th member of an aggregate type.
Definition meta.hpp:687
decltype(auto) member_ref(T &&value) noexcept
Get a reference to the I-th member of an aggregate object.
Definition meta.hpp:763
constexpr int enum_min_v
The minimum reflection scan value for enumeration type E.
Definition meta.hpp:93
constexpr bool has_member(std::string_view name)
Test whether an aggregate has a reflected member with the given name.
Definition meta.hpp:666
consteval auto member_names()
Get all member names of an aggregate type at compile-time.
Definition meta.hpp:632
consteval auto enum_entries()
Get all enumerator entries (value, name) of an enumeration type at compile-time.
Definition meta.hpp:355
consteval auto enum_values()
Get all enumerator values of an enumeration type at compile-time.
Definition meta.hpp:193
constexpr auto member_count_v
Number of members in an aggregate type as an inline constant.
Definition meta.hpp:486
constexpr auto enum_index(E value) -> std::optional< std::size_t >
Get the index of an enumerator value in enum_values<E>().
Definition meta.hpp:380
constexpr void for_each_member(T &&value, F &&func) noexcept(noexcept(detail::for_each_member_impl(std::forward< T >(value), std::forward< F >(func), std::make_index_sequence< member_count_v< std::remove_cvref_t< T > > >{})))
Visit each member of an aggregate object together with its member name.
Definition meta.hpp:842
consteval auto member_name() noexcept
Get the name of a specific member of an aggregate type at compile-time.
Definition meta.hpp:558
constexpr auto enum_underlying(E value) noexcept -> std::underlying_type_t< E >
Return the underlying integer value of an enumerator.
Definition meta.hpp:414
constexpr auto enum_cast(std::string_view name) -> std::optional< E >
Cast an enumerator name to its corresponding enumerator value.
Definition meta.hpp:399
constexpr constant_arg_t< V > constant_arg
A ready-to-use constant_arg_t<V> object for a compile-time value.
Definition utility.hpp:40
Definition lock.hpp:21
A placeholder type implicitly convertible to any type.
Definition utility.hpp:48
Customize the reflection range or explicit values for an enumeration.
Definition meta.hpp:46
static constexpr int max
Definition meta.hpp:48
static constexpr int min
Definition meta.hpp:47
A compile-time string type with fixed length and constexpr operations.
Format a type name for documentation-friendly display.
Definition type_name.hpp:72