C++ Logo


Advanced search

Feedback on P3096 Function Parameter Reflection in Reflection for C++26

From: Guillaume Racicot <gracicot42_at_[hidden]>
Date: Sun, 17 Mar 2024 22:27:44 -0400
Hi, I was reading P3096R0 and I would like to give some feedback.

As it's one of the very few times I see dependency injection mentioned
in a paper, I felt maybe my feedback would help shape the feature as
I'm the maintainer/implementer of a DI library named kangaru. In fact,
I'm in the process of completely rewriting it in C++20 with simple and
composable primitives for dependency injection.

I have multiple challenges today that I think this paper, or adjacent
papers could help greatly with.

So for type based dependency injection, contrary to implementations I
saw in the wild, it is possible in C++20 to reliably inspect the
parameter types of a function using conversion operators. And by that
I mean the type, if it's by value or by reference, and even the
precise reference type and constness. Doing that portably is
technically possible, despite the fact that compilers kinda do what
they want in the area of templated conversion operators. If we ignore
mutable constexpr programming, you can't form a type list of all the
parameters, but it is nevertheless possible to perform the injection
through the conversion operators. If on the contrary you allow
yourself to do mutable constexpr, you can in fact implement precise
type reflection today.

The fact that this is possible to do today but only through horrible
hacks is a good argument for this paper. It would only make existing
practices easier to implement and avoid pitfalls.

Other than that, the reflection mechanism proposed in the paper would
help replacing conversion operators, but not completely replace them
for me. To replace them in my library, I would need more information
about the function being reflected than what is being proposed. Note
that those additions do not exclude what's in the paper, but would add
to it.

1. I would need to ask the compiler: "Is the function being reflected
overloaded? If so, what is the amount of parameters and type of each
overloads?" I would need this information to get the amount of
parameters and their types to be able to do proper matching on them,
or diagnose issues. The paper has a compiler explorer link that kinda
does reflection over overloaded sets but for constructors only, and it
does so through iterating over all members of a type. This mechanism
would not work well with free functions. Having an explicit mechanism
to reflect on overloaded functions would help. I'm not sure it's a
problem we can solve by lifting free functions.

2. I would also need to ask the compiler: "Is the function being
reflected templated?" This is because when composing dependency
injection primitives, I need wrapper functions that forward to other
wrapped functions. To do that it requires templates and forwarding.
Whether the function is templated or not would let me decide if I want
to call the function using DI or forward the context to the next
function wrapper. As far as I know, P2996 adds
ents` but it is only defined for class
templates, not for functions.

3. Going further on point 2, if it's possible to reflect on the
template itself and know which parameter is deduced, it would allow me
to remove the last need for conversion operators, which is partial DI.
Today, based on which parameter is concrete or deduced I either
perform DI or simply forward the parameter. This is due to the nature
of conversion operators, which are not called when deduction happens.
If I can reflect which parameter is deduced and which are concrete
types, I can apply the injection only on the parameter with concrete
types and forward the rest for referred DI in the next wrapper

With those three points, I could completely replace my use of
conversion operators within the library.

I was also thinking of other additions that could be worth exploring.
Those additions would allow powerful constructs that are not possible
to do today. They could be done in this or subsequent papers (if
deemed feasible):

1. Add a `std::meta::lift_function` in which you give a
`std::meta::info` of an overload set, and the function would produce a
perfectly lifted function. That lifted function would be a closure
type that has all the same parameters with the same names and the same
overloads. No more writing those lambdas by hand!

2. Reflection on function call. For such expression `f(a, b, c)`
getting the meta info for the `f` that would be called.

3. In addition to number 2, Transparent lifted function and
transparent function wrapper. It could be done with reflection on a
function call in return type expression. For example, for such a
lambda `[](auto a, auto b) -> decltype(f(a, b)) { ... }` get which
overload of `f` would be called with given arguments.

4. Extracting what constraints apply to which parameter. If I can get
the constraints attached to a template parameter, I could perform
concept based DI which I don't think any other language can do today.

I can give code examples for my points and my ideas, and I can help
write papers if there's interest in some of those.

Thank you for your time, and thank you for that paper, and hopefully
I'm not too late to bring that before the discussion in the meeting :)

Received on 2024-03-18 02:27:41