No, uintptr_t cannot be used meaningfully to avoid undefined behavior when doing pointer arithmetic.
On the one hand, at least in C there is no guarantee that uintptr_t even exists. The requirement is that any value of type void* can be converted to uintptr_t and vice versa, which gives the original value without loss of information. In principle, there may not be some unsigned integer type wide enough to hold all pointer values. (I suppose the same applies to C ++, since C ++ inherits most of the C standard library and defines it by reference to the C standard.)
Even if uintptr_t exists, there is no guarantee that this arithmetic operation in the uintptr_t value does the same as the corresponding operation on the pointer value.
For example, I worked on systems (Cray, T90, and SV1 vector systems) on which byte pointers are implemented in software. A native address is a 64-bit address that refers to a 64-bit word; no hardware support for byte addressing. The char* or void* pointer consists of a 3-bit offset word pointer stored in unused, unused, high-order bits. The conversion between integers and pointers just copies the bit. Thus, incrementing a char* will cause it to point to the next 8-bit byte in memory; the increment a uintptr_t obtained by the conversion of a char* should have promoted it to indicate the next 64-bit word.
Here is just one example. More generally, conversions between pointers and integers are implementation specific, and the locale does not guarantee the semantics of these conversions (except, in some cases, converting back to a pointer).
So, you can convert the pointer value to uintptr_t (if this type exists) and perform arithmetic on it without risking undefined behavior, but the result may or may not be significant.
It happens that on most systems, matching between pointers and integers is easier, and you can probably get away with such a game. But you better use pointer arithmetic directly and just be careful to avoid any invalid operations.