C++ reflections: Getting a reflection of a type of a pointer to member, from a reflection of a member is difficult
I am going to argue that we're missing a fairly basic metafunction in C++26. While there are ways around it, none is without downsides. Let's explore!
The title is a mouthful, but I'm talking about this:
struct showcase {
void mem_fun() const {};
};
constexpr std::meta::info mem_fun_refl = ^^showcase::mem_fun;
constexpr std::meta::info mem_fun_ptr_refl = add_pointer(type_of(mem_fun_refl));
Unfortunately, that last line is just nonsense, because the type of `mem_fun` is `void() const`,
which looks similar to a free function type, with the extra cv qualifier.
`add_pointer`, whether the one in `<meta>` or in `<type_traits>` does not work there and
just produces the same type, unchanged.
Things get more confusing if `mem_fun()` does not have a cv qualifier. In that case, its type looks
just like a free function type. Now `add_pointer()` compiles and does the wrong thing.
So `add_pointer()` is not useful at all for this purpose.
One option that sometimes works is address-splicing:
constexpr std::meta::info mem_fun_refl = ^^showcase::mem_fun;
constexpr std::meta::info mem_fun_ptr_refl = ^^decltype(&[:mem_fun_refl:]);
That comes with a constraint that `mem_fun_refl` is a constant expression *in the current context*.
In other words, this approach fails when `mem_fun_refl` is an argument to a `consteval` function. I.e. the following does not compile:
consteval std::meta::info to_ptr(std::meta::info thing) {
return ^^decltype([:thing:]);
}
Okay, but we can make `std::meta::info thing` a template parameter. This is what I ended up doing in my project.
template<std::meta::info thing>
consteval std::meta::info to_ptr() {
return ^^decltype([:thing:]);
}
That works, but now whoever calls `to_ptr<thing>()` needs to also have `thing` be a constant expression in that scope. In other words, we end up with propagating "this has to be a template" up the call stack.
One last attempt: can we manually assemble a pointer to member's type? Something like
[:return_type:] ([:parent_type:]::*)([:parameter_types:]...)
[:return_type:] ([:parent_type:]::*)([:parameter_types:]...) const
[:return_type:] ([:parent_type:]::*)([:parameter_types:]...) const volatile
[:return_type:] ([:parent_type:]::*)([:parameter_types:]...) volatile
[:return_type:] ([:parent_type:]::*)([:parameter_types:]...) noexcept
[:return_type:] ([:parent_type:]::*)([:parameter_types:]...) const noexcept
[:return_type:] ([:parent_type:]::*)([:parameter_types:]...) const volatile noexcept
[:return_type:] ([:parent_type:]::*)([:parameter_types:]...) volatile noexcept
We have all of the needed info:
bool is_noexcept = std::meta::is_noexcept(thing);
bool is_const = std::meta::is_const(thing);
bool is_volatile = std::meta::is_const(thing);
auto return_t = std::meta::return_type_of(thing);
auto parameters = parameters_of(type_of(thing));
auto parent = type_of(parent_of(thing));
The trouble is now doing the manual assembly without actually splicing anything, because `thing` might not be a constant expression.
This is doable, but is quite involved:
template<bool is_const, bool is_volatile, bool is_noexcept, typename R, typename P, typename...Args>
struct assemble_ptr_to_member {
using ptr = std::condtional_t<is_const,
std::conditional_t<is_volatile,
std::conditional_t<is_noexcept, R (P::*)(Args...) const volatile noexcept, R (P::*)(Args...) const volatile>,
std::conditional_t<is_noexcept, R (P::*)(Args...) const noexcept, R (P::*)(Args...) const>,
std::conditional_t<is_volatile,
std::conditional_t<is_noexcept, R (P::*)(Args...) volatile
Post #25143
11