C++ Logo

std-proposals

Advanced search

[std-proposals] uintfuncptr_t (and mandatory uintptr_t)

From: Frederick Virchanza Gotham <cauldwell.thomas_at_[hidden]>
Date: Wed, 2 Jul 2025 16:33:46 +0100
First let's talk about uintfuncptr_t.

The Standard says that the address of any non-member function can be
safely cast to:

    void(*)(void)

and then back to the original function pointer type without any loss
of information. So we already have a 'unified' function pointer type.
There should be an integer type corresponding to this, and it should
be std::uintfuncptr_t.

So that's code memory dealt with.

Moving onto data memory now . . . which by the way might need bigger
or smaller pointers than code memory (e.g. Harvard Architecture).

Let's talk about uintptr_t, and about why it's optional for compilers
to provide. I've written assembler for around about five or six CPU
instructions sets, and all these machines always treat a pointer just
like an integer. For example on 64-Bit x86 machines, if you want to
increment an integer, it will be:

    add rax, 1

Similarly if you want to increment a pointer by one byte, it's:

    add rax, 1

And so if the compiler can provide a " char * " type of suitable width
to hold a memory address, then it can provide an integer type of the
same width. No rocket science here.

I've been trying to find examples of where this might not be possible,
and I keep getting pointed to x86 Real Mode. It turns out that in the
1980's, you could use the Turbo C compiler to make pointers as
follows:

    char near *p;
    char far *p;

The former was 2 bytes, and it was just an offset into an unknown
segment. The latter was 4 bytes, and contained both the segment
address and the offset. In a setup like this, where a pointer can be
modified with further qualifiers such as "near" and "far", I think
there should be corresponding integer types:

    std::uintptr_near_t ( sizeof == 2 )
    std::uintptr_far_t ( sizeof == 4 )

And I think that 'std::uintptr_t' should be a typedef for whichever
one is the default when you do:

    char *p;

And so in x86 Real Mode, the default is a near pointer and so
uintptr_t should be 16-Bit.

There's a simple universal implementation for these two types:

#include <cstdint> // uintmax_t
#include <type_traits> // conditional

namespace std {
  typedef conditional_t< sizeof(unsigned) == sizeof(char*), unsigned,
            conditional_t< sizeof(long unsigned) == sizeof(char*),
long unsigned,
              conditional_t< sizeof(long long unsigned) ==
sizeof(char*), long long unsigned,
                conditional_t< sizeof(short unsigned) ==
sizeof(char*), short unsigned,
                  conditional_t< sizeof(char unsigned) ==
sizeof(char*), char unsigned,
                    conditional_t< sizeof(uintmax_t) == sizeof(char*),
uintmax_t, void > > > > > > uintptr_tZ;

  typedef conditional_t< sizeof(unsigned) == sizeof(void(*)(void)), unsigned,
           conditional_t< sizeof(long unsigned) ==
sizeof(void(*)(void)), long unsigned,
            conditional_t< sizeof(long long unsigned) ==
sizeof(void(*)(void)), long long unsigned,
             conditional_t< sizeof(short unsigned) ==
sizeof(void(*)(void)), short unsigned,
              conditional_t< sizeof(char unsigned) ==
sizeof(void(*)(void)), char unsigned,
               conditional_t< sizeof(uintmax_t) ==
sizeof(void(*)(void)), uintmax_t, void > > > > > > uintfunctptr_tZ;
}

Received on 2025-07-02 15:33:56