Skip to content

Commit d63cea6

Browse files
committed
Add 'reflect_invoke' metafunction and motivating example.
1 parent b9a991a commit d63cea6

File tree

1 file changed

+101
-1
lines changed

1 file changed

+101
-1
lines changed

2996_reflection/reflection.md

Lines changed: 101 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,8 @@ Since [@P2996R1], several changes to the overall library API:
3030

3131
* added `qualified_name_of` (to partner with `name_of`)
3232
* removed `is_static` for being ambiguous, added `has_internal_linkage` (and `has_linkage` and `has_external_linkage`) and `is_static_member` instead
33-
* added `is_class_member` and `is_namespace_member`
33+
* added `is_class_member`, `is_namespace_member`, and `is_concept`
34+
* added `reflect_invoke`
3435

3536
Since [@P2996R0]:
3637

@@ -999,6 +1000,86 @@ Thus, the only instantiations of `TU_Ticket::Helper` occur because of the call t
9991000
10001001
[On Compiler Explorer](https://godbolt.org/z/1vEjW4sTr).
10011002
1003+
## Emulating typeful reflection
1004+
Although we believe a single opaque `std::meta::info` type to be the best and most scalable foundation for reflection, we acknowledge the desire expressed by SG7 for future support for "typeful reflection". The following demonstrates one possible means of assembling a typeful reflection library, in which different classes of reflections are represented by distinct types, on top of the facilities proposed here.
1005+
1006+
::: bq
1007+
```cpp
1008+
// Represents a 'std::meta::info' constrained by a predicate.
1009+
template <std::meta::info Pred>
1010+
requires (type_of(reflect_invoke(Pred, {reflect_value(^int)})) == ^bool)
1011+
struct metatype {
1012+
// The underlying 'std::meta::info' value.
1013+
const std::meta::info value;
1014+
1015+
// Construction is ill-formed unless predicate is satisfied.
1016+
consteval metatype(std::meta::info r) : value(r) {
1017+
if (![:Pred:](r))
1018+
throw "Reflection is not a member of this metatype";
1019+
}
1020+
1021+
// Cast to 'std::meta::info' allows values of this type to be spliced.
1022+
consteval operator const std::meta::info&() const { return value; }
1023+
1024+
static consteval bool check(std::meta::info r) { return [:Pred:](r); }
1025+
1026+
static consteval metatype<Pred> make(std::meta::info r) { return {r}; }
1027+
};
1028+
1029+
// Type representing a "failure to match" any known metatypes.
1030+
struct unmatched { static consteval unmatched make(...) { return {}; } };
1031+
1032+
// Returns the given reflection "enriched" with a more descriptive type.
1033+
template <typename... Choices>
1034+
constexpr auto enrich = [](std::meta::info r) consteval {
1035+
std::array checks = {^Choices::check...};
1036+
std::array ctors = {^Choices::make...};
1037+
1038+
auto choice = ^unmatched;
1039+
for (auto [check, make] : std::views::zip(checks, makes))
1040+
if (value_of<bool>(reflect_invoke(check, {reflect_value(r)}))) {
1041+
choice = make;
1042+
break;
1043+
}
1044+
return reflect_invoke(choice, {reflect_value(r)});
1045+
}
1046+
```
1047+
:::
1048+
1049+
We can leverage this machinery to select different function overloads based on the "type" of reflection provided as an argument.
1050+
1051+
::: bq
1052+
```cpp
1053+
using type_t = metatype<^std::meta::is_type>;
1054+
using fn_t = metatype<^std::meta::is_function>;
1055+
1056+
// Example of a function overloaded for different "types" of reflections.
1057+
void PrintKind(type_t) { std::println("type"); }
1058+
void PrintKind(fn_t) { std::println("function"); }
1059+
void PrintKind(unmatched) { std::println("unknown kind"); }
1060+
1061+
int main() {
1062+
constexpr auto enrich = ::enrich<type_t, fn_t>;
1063+
1064+
// Demonstration of using 'enrich' to select an overload.
1065+
Printer::PrintKind([:enrich(^int):]); // "type"
1066+
Printer::PrintKind([:enrich(^main):]); // "function"
1067+
Printer::PrintKind([:enrich(^3):]); // "unknown kind"
1068+
}
1069+
```
1070+
1071+
Note that the `metatype` class can be generalized to wrap values of any literal type, or to wrap multiple values of possibly different types. This has been used, for instance, to select compile-time overloads based on: whether two integers share the same parity, the presence or absence of a value in an `optional`, the type of the value held by a `variant` or an `any`, or the syntactic form of a compile-time string.
1072+
1073+
Achieving the same in C++23, with the same generality, would require spelling the argument(s) twice: first to obtain a "classification tag" to use as a template argument, and again to call the function, i.e.,
1074+
1075+
::: bq
1076+
```cpp
1077+
Printer::PrintKind<classify(^int)>(^int).
1078+
// or worse...
1079+
fn<classify(Arg1, Arg2, Arg3)>(Arg1, Arg2, Arg3).
1080+
```
1081+
:::
1082+
10021083
# Proposed Features
10031084

10041085
## The Reflection Operator (`^`)
@@ -1409,6 +1490,9 @@ namespace std::meta {
14091490
template<typename T>
14101491
consteval auto reflect_value(T value) -> info;
14111492

1493+
// @[reflect_invoke](#reflect_invoke)@
1494+
consteval auto reflect_invoke(info target, span<info const> args) -> info;
1495+
14121496
// @[define_class](#data_member_spec-define_class)@
14131497
struct data_member_options_t;
14141498
consteval auto data_member_spec(info class_type,
@@ -1659,6 +1743,22 @@ namespace std::meta {
16591743
16601744
This metafunction produces a reflection representing the constant value of the operand.
16611745
1746+
### `reflect_invoke`
1747+
1748+
:::bq
1749+
```c++
1750+
namespace std::meta {
1751+
consteval auto reflect_invoke(info target, span<info const> args) -> info;
1752+
}
1753+
```
1754+
:::
1755+
1756+
This metafunction produces a reflection of the value returned by a call expression.
1757+
1758+
Letting `F` be the entity reflected by `target`, and `A_0, ..., A_n` be the sequence of entities reflected by the values held by `args`: if the expression `F(A_0, ..., A_N)` is a well-formed constant expression evaluating to a type that is not `void`, and if every value in `args` is a reflection of a constant value, then `reflect_invoke(target, args)` evaluates to a reflection of the constant value `F(A_0, ..., A_N)`.
1759+
1760+
For all other invocations, `reflect_invoke(target, args)` is ill-formed.
1761+
16621762

16631763
### `data_member_spec`, `define_class`
16641764

0 commit comments

Comments
 (0)