3

So the following code works great:

void function() { }
std::cout << typeid(decltype(function)).name() << '\n';

but the following code doesn't compile:

struct object {
    void function() { }
}
std::cout << typeid(decltype(object::function)).name() << '\n';

Why?

The member function has a type, just as the normal function has a type right? I would assume it looks like this:

void func(object*, void)

The strange thing is, I can get the pointer just fine with:

decltype(&object::function)

That ends up being this:

void (__thiscall object::*)(void)

If I can get the pointer to it, why can't I get the type of it? Just like I can with the normal function. I tried to remove the pointer after getting the pointer to member function type:

template <typename T>
struct remove_pointer { using type = T; }

template <typename T>
struct remove_pointer<T*> { using type = T; }

std::cout << typeid(remove_pointer<decltype(&object::function)>::type).name() << '\n';

It doesn't remove the pointer, but instead of failing compilation, it just prints out the same thing that it does when I don't try to remove the pointer.

This is all very confusing, can someone help me make sense of why I can't access the actual type of a member function?

HolyBlackCat
  • 78,603
  • 9
  • 131
  • 207
Nik Tedig
  • 453
  • 2
  • 6
  • Might be an interesting experiment: what happens if you change the non-specialized `remove_pointer` to `template struct remove_pointer { using type = int; };`? (Don't change the specialized version. This is basically a way to see which template is being used.) – JaMiT May 28 '22 at 19:58
  • @JaMit Nice thinking. Turns out the specialization was never being used. I don't really understand the whole pointer to member thing, but I tried replacing T* with T object::* in the specialization and now the specialization route is being taken. The member function's true type is now successfully being shown. I'll edit the post. – Nik Tedig May 28 '22 at 20:04
  • It's discouraged to modify the question (fix code, etc) after getting an answer, as this can be confusing for future readers. – HolyBlackCat May 28 '22 at 21:34

2 Answers2

3

Member pointers are not pointers. To strip one from a type, you need something like this:

template <typename> struct RemoveMemPtr {};
template <typename X, typename Y> struct RemoveMemPtr<X Y::*> {using type = X;};

However, this won't give you any new information. Given ReturnType (Class::*)(Params...), it will just return ReturnType(Params...), similar to how a regular function pointer type can be transformed to a function.


decltype(object::function) doesn't work simply because the standard says so. When you name a member function, you must either call it immediately, or form a pointer to it.


Member functions formally do have a type, but there's no way to determine this type apart from forming a member pointer and stripping the pointer part from it. It's the same type as a free function with the same signature would have (implicit this parameter is not included). Except that member functions can have const/volatile/&/&& qualifiers, and free functions can't.

As a piece of trivia, you can use those types to declare member functions:

using F = void(int) const;

struct A
{
    F func; // void func(int) const;
};
HolyBlackCat
  • 78,603
  • 9
  • 131
  • 207
  • I would just add that `decltype(free_function)` works only because the token `free_function` refers both to the function and to a pointer to it, i.e. `&` is implicit. – Quimby May 28 '22 at 20:09
  • 1
    @Quimby Nah. Just like arrays are not pointers, nor are functions. The conversion happens implicitly in some contexts, but not by default, otherwise you wouldn't be able to (directly) form a reference to a function. And `decltype(function)` returns the function type without the `*`: https://gcc.godbolt.org/z/vjedza99a – HolyBlackCat May 28 '22 at 20:11
  • I guess I follow, it's still not making a whole lot of sense though. Why in the name of all that is good in this world would the language designers think this arbitrary limitation is a good idea? If I can achieve my goal by stripping the decltype of the pointer to the function, why not just let me achieve it in a more straight-forward way by making decltype(object::function) legal? – Nik Tedig May 28 '22 at 20:14
  • @NikTedig Who knows. Allowing this just for decltype makes the language rules a bit more tricky (on the other hand, `decltype` has special handling for variables, so could as well reuse those rules for functions...). Allowing this everywhere would be broken, because with such type it would look like a regular free function, without being one. – HolyBlackCat May 28 '22 at 20:18
  • @HolyBlackCat I don't follow, wouldn't you still have the scope resolution operator in the decltype, thereby allowing you to see that it's a member function? I guess you could confuse it with a free function in a namespace, but you could confuse the current version with the pointer to a free function in a namespace as well couldn't you? Since you can do decltype(&function) for free functions as well. – Nik Tedig May 28 '22 at 20:22
  • 1
    @NikTedig If only allowed in `decltype` yes, it would be ok. But outside of decltype, if it had the same type as a free function would have, what would happen if you assigned it to a regular (non-member) function pointer? – HolyBlackCat May 28 '22 at 20:24
  • @HolyBlackCat I think the simplest solution to that would be to just have the this pointer be part of the member functions type. That way, it's distinguishable from free-floating regular functions. – Nik Tedig May 28 '22 at 20:38
  • @NikTedig It gets quirky very fast. :) When multiple inheritance is involved (when base class is at non-zero offset in the derived class), and you can cast a pointer to a base class function to a pointer to a derived class function (that's allowed), the resulting member pointer has to store the offset that needs to be applied to `this` to call it. [See this for details](https://stackoverflow.com/a/12006882/2752075). You can't fit this offset into a regular function pointer. – HolyBlackCat May 28 '22 at 20:46
  • @HolyBlackCat I think I might understand: So allowing decltype of member functions would leave you open to take that type and create a pointer type, which should represent a pointer to the member function, except that it doesn't, since pointers to member functions are often bigger than normal pointers to functions. So to avoid this fiasco, they just force you to only get the type of the POINTER to the member function, since the special information is already reflected in the pointer type. What still doesn't make sense is that I can easily get around this by removing the pointer afterwards. – Nik Tedig May 28 '22 at 21:09
  • @NikTedig That's not exactly what I meant. Allowing it in `decltype` would be ok, I think. It would not be ok to allow it outside of decltype. – HolyBlackCat May 28 '22 at 21:11
  • @HolyBlackCat I think we're on the same page then. Although I think I'm slowly starting to see why they don't let you do it in decltype. I can imagine one might make some stupid mistakes if the compiler let you get the type of the member function just like that. Like I said, you could create a pointer out of the type and mistakenly assume it could represent a pointer to the member function, when in reality it just represents the equivalent free function (because different pointer sizes). I don't know what "outside of decltype" means. Getting type of expression only works in decltype right? – Nik Tedig May 28 '22 at 21:21
  • @NikTedig I meant allowing syntax `MyClass::MyFunc` (without `&` or `()`) not only in decltype, but elsewhere. Yep, sounds correct. – HolyBlackCat May 28 '22 at 21:24
  • @HolyBlackCat You cannot refer to a non-static member function in an expression other than by forming a pointer to it or calling it, so its type (with the class name and cv-qualifiers and reference suffix thingies stripped) is not really useful for anything. – n. m. could be an AI May 28 '22 at 21:26
  • @n.1.8e9-where's-my-sharem. Yep, I was trying to explain that it wouldn't feasible. :) – HolyBlackCat May 28 '22 at 21:31
0

Your remove_pointer is misnamed. It turns a type "pointer to a member of type T of class object" to T, which is not directly related to the original type and is incompatible with entities of that type. The crucial piece of information about the original being a member of object is lost. The template should better be named something like remove_membership.

For example, it transforms the type of &object::function, which is void (object::*)(), to void(). It isn't at all clear why you would want void() while working with object::function, as it is not compatible with void() in any way.

Removing membership is a non-trivial, not generally useful transformation, so there is no built-in syntax to achieve it. If you need it, you just write a template that does it.

n. m. could be an AI
  • 112,515
  • 14
  • 128
  • 243