There are four completely unrelated classes of pointer types in the C++ language: object pointers, function pointers, non-static data member pointers, and non-static member function pointers. The term "pointer" generally only applies to object and function pointer types [basic.compound]/3:
[…] Except for pointers to static members, text referring to “pointers” does not apply to pointers to members. […]
Pointers and pointers to non-static members are actually treated as two completely separate kinds of compound types altogether [basic.compound]/1 (which makes sense since non-static member pointers are more like relative offsets and less like actual addresses).
Except for a conditionally-supported conversion between object and function pointers, the semantics of which (if supported at all) will be implementation-defined [expr.reinterpret.cast]/8, there is no way to convert between these four classes of pointer types.
However, the standard does specify interconvertibility amongst object pointers [expr.reinterpret.cast]/7, interconvertibility amongst function pointers [expr.reinterpret.cast]/6, interconvertiblity amongst data member pointers [expr.reinterpret.cast]/10.2, and interconvertibility amongst member function pointers [expr.reinterpret.cast]/10.1.
As a result, while there is no common pointer type that all other pointer types are related to in general, it is well-defined behavior to cast any object pointer to some arbitrary object pointer type and back. It is well-defined behavior to cast any function pointer to some arbitrary function pointer type and back. It is well-defined behavior to cast any data member pointer to some arbitrary data member pointer type and back. And it is well-defined behavior to cast any member function pointer to some arbitrary member function pointer type and back. And one thing all these different classes of pointer types have in common is that they're all object types [basic.types]/8.
While this does not strictly guarantee that, e.g., all member function pointer types are the same size, it does implicitly establish that any object of some member function pointer type can effectively be used to store any member function pointer value. There may still be member function pointer types larger than others, but they could not possibly hold more information than others since the standard requires that the conversion to and from any other member function pointer type must not lose information (the original value can always be restored). The same argument works analogously for all the other classes of pointer types.
Based on all this, I would argue that it's technically impossible to find "the largest pointer type" in standard C++. However, while it may technically be impossible to find the largest pointer type itself, based on the argument above, it is definitely possible to find an upper bound for the amount of storage needed to reliably store any value of pointer type. While those two are technically different things, in practice, the second one is most likely almost as good as the first (no reasonable compiler will just randomly add lots of padding bits to the value representation of some pointer type just because doing so is technically legal). At least I'm having a hard time imagining what else than store pointer values you could possibly be wanting to do with the kind of information you're asking for.
Using, for example
using generic_obj_ptr = void*;
using generic_fun_ptr = void (*)();
class dummy_t;
using generic_dat_mem_ptr = dummy_t dummy_t::*;
using generic_mem_fun_ptr = void (dummy_t::*)();
you can compute
auto obj_ptr_size = sizeof(generic_obj_ptr_t);
auto fun_ptr_size = sizeof(generic_fun_ptr_t);
auto dat_mem_ptr_size = sizeof(generic_dat_mem_ptr_t);
auto mem_fun_size = sizeof(generic_mem_fun_ptr_t);
auto max_ptr_size = std::max({ sizeof(generic_obj_ptr_t), sizeof(generic_fun_ptr_t), sizeof(generic_dat_mem_ptr_t), sizeof(generic_mem_fun_ptr_t) });
auto max_ptr_align = std::max({ alignof(generic_obj_ptr_t), alignof(generic_fun_ptr_t), alignof(generic_dat_mem_ptr_t), alignof(generic_mem_fun_ptr_t) });
or just use
using ptr_storage_t = std::aligned_union<0U, generic_obj_ptr_t, generic_fun_ptr_t, generic_dat_mem_ptr_t, generic_mem_fun_ptr_t>;
or even
using any_ptr_t = std::variant<generic_obj_ptr_t, generic_fun_ptr_t, generic_dat_mem_ptr_t, generic_mem_fun_ptr_t>;
or in its pure form:
using any_ptr_t = std::variant<void*, void (*)(), dummy_t dummy_t::*, void (dummy_t::*)()>;
as storage in which any object pointer value can be stored when cast to and from void*
, any function pointer value can be stored when cast to and from void (*)()
, any data member pointer can be stored when cast to and from dummy_t dummy_t::*
, and any member function pointer can be stored when cast to and from void (dummy_t::*)()
.
play with it here
The task of wrapping this in a class that takes care of all the casting for storing arbitrary values of any pointer type (don't forget to deal with possible cv qualification), shall be left as an exercise for the reader, mainly because I would really like to sleep well tonight…
void (Class::* func)()
, and pointers to member dataint Class::* mem
? – Jessalynstd::unique_ptr
,std::shared_ptr
, and the old-and-brokenstd::auto_ptr
. – Abmvoid *
, and now I know that's not true. So I'd at least like to be able to have anstd::array<std::byte, max_size_of_any_ptr>
to put type-erased pointers in. – Incestuous