[PATCH] C++: explicit consuming relocation for mpz_class

Marc Glisse marc.glisse at inria.fr
Sat Oct 17 13:37:34 CEST 2026


Hello,

Thanks for the contribution.

I am generally a proponent of destructive move (aka relocation), but I 
need some convincing that your approach is really the way to go. What is 
the advantage compared to just documenting that mp*_class is trivially 
relocatable, and letting users handle these types the same way they handle 
other trivially relocatable types (with memcpy, memmove, etc)? Your patch 
adds functions specific to GMP, so users would have to write special code 
to handle GMP wherever they want to relocate. Is this done to future-proof 
the code, in case we decide in the future that GMP types should have 
pointers to themselves in some cases and thus wouldn't be trivial to 
relocate anymore?

It is also a bit annoying to end up with so many functions, moving 1 
object, moving a range of objects, moving a range to an overlapping 
destination. It feels like some of those should be the responsibility of 
the compiler (possibly with semi-portable extensions like __restrict), 
although I know compilers haven't always been great at this, and some 
information is hard to convey to the compiler, so extra functions can make 
sense.

-- 
Marc Glisse


On Wed, 30 Sep 2026, Roberto Bagnara wrote:

> Dear GMP developers,
>
> I would like to propose an addition to GMP's C++ interface: explicit
> consuming relocation of mpz_class objects, including operations on
> whole ranges. The attached gmp-6.3.0-relocation.patch is against GMP
> 6.3.0 and includes documentation and a regression test in GMP's own
> test suite.
>
> The intended users are authors of containers that manage object
> storage and lifetimes explicitly. Such containers need to grow arrays
> of integers, compact storage, or shift a range to make or close a
> gap. They often need to preserve the integer values and their existing
> limb allocations while discarding or reusing the old object slots. A
> GMP-supported operation would let them express this directly, without
> duplicating assumptions about the representation and ownership of
> mpz_class in application code.
>
> Ordinary mpz_class moves already transfer ownership without copying
> limbs.  The distinction here is the source contract: a move leaves a
> source that can be destroyed normally, whereas these containers can
> relinquish the source slots entirely. An explicit consuming operation
> need not reset those slots or arrange for their subsequent
> destruction. For ranges, it also allows GMP to implement the transfer
> using a bulk descriptor copy or move, including overlapping shifts,
> instead of requiring callers to express it as a sequence of ordinary
> object moves. This is an additional facility, not a change to the
> existing copy, move or destruction semantics.
>
> The patch proposes four static members, available in C++11 and later:
>
>  mpz_class::relocate_into(to, from) Relocate one integer into
>    distinct destination storage.
>
>  mpz_class::relocate_range(to, from, count) Relocate a range into
>    disjoint destination storage.
>
>  mpz_class::relocate_shift_right(from, count, shift)
>  mpz_class::relocate_shift_left(from, count, shift) Relocate a range
>  within one allocation, allowing overlap.
>
> All four are noexcept. They transfer the existing limb allocations
> without allocating, reallocating, freeing or copying limbs. Their work
> depends on the number of integer descriptors, independently of the
> integers' sizes.  For example, a custom array can grow by allocating
> destination storage, relocating its live range, and releasing the old
> storage without invoking the consumed source objects'
> destructors. Overlapping operations allow the corresponding insertion,
> erasure and compaction implementations.  Existing standard containers
> would not automatically use these functions.
>
> The ownership contract is deliberately explicit. Source-only slots are
> consumed: they must not be read as integers, assigned to, or
> destroyed.  The caller must either reconstruct an mpz_class there or
> release the storage without those destructors. In particular, an
> ordinary local variable must be reconstructed before its implicit
> destructor runs.  Consumption transfers ownership; it does not itself
> end the source object's C++ lifetime. With overlapping ranges, slots
> in the intersection remain live and hold their new destination values.
>
> Destination-only slots must be suitably aligned unconstructed or
> consumed storage; any existing owned value must first be
> destroyed. The manual specifies the complete range, overlap and size
> requirements, excludes base-class subobjects, and requires valid
> nonnull storage even for zero-count or zero-shift calls. It includes
> an allocation/relocation/ destruction example and explains how to
> reuse consumed slots.
>
> The implementation establishes destination object lifetimes using
> placement new with private constructors before transferring descriptor
> representations.  For overlapping operations, only destination-only
> objects are constructed.  Compile-time checks enforce the
> representation properties used by the bulk operations: a
> standard-layout, descriptor-only wrapper and a trivially
> default-constructible, trivially copyable descriptor. The argument
> relies on copying the representations of live descriptor subobjects;
> it does not treat mpz_class itself as trivially copyable. It requires
> neither C++20 implicit object creation nor compiler-specific
> relocation extensions.
>
> At -O2 in C++17 mode, the four primitives produced the same
> instruction bodies as their direct descriptor-transfer baselines with
> Apple Clang 17, GCC 16 (a development snapshot) and GCC 12.2 on the
> tested macOS/AArch64, Ubuntu/x86_64 and Debian/AArch64 systems,
> respectively. This supports keeping the cost of the low-level
> transfers while giving them an explicit lifetime and ownership
> contract, not a claim of a speedup over every implementation using
> ordinary moves. Unoptimized builds can retain the empty construction
> loops; the allocation and ownership guarantees do not depend on
> optimization.
>
> The GMP test suite completes without failures on all three systems,
> with one expected skip per run. The added regression checks complete
> values and limb ownership, zeros and large signed values, destination
> reuse, source reconstruction, overlap in both directions, boundary and
> no-op cases, and ordinary copy/move behavior. It counts allocation,
> reallocation and free calls while retaining GMP's native test
> allocator checks. In additional macOS validation, the C++ regression
> and inline operations pass ASan/UBSan; the underlying GMP C library
> was not sanitizer-instrumented. Installed consumers were tested there
> in C++11, C++17 and C++20 modes, with separate compile-only checks for
> C++98 and disabled C++11 features.  Windows has not been tested.
>
> The class layout is unchanged, but its definition changes. The
> proposal therefore includes a feature macro and an optional
> configuration link check against a marker in libgmpxx; relocation does
> not call that marker. These checks help detect mismatched headers and
> libraries, but do not replace rebuilding and using the relevant C++
> components consistently. The names, API shape and detection convention
> are all open for discussion.
>
> The patch includes the generated tests/cxx/Makefile.in changes so it
> can also be applied to the release tarball without regenerating the
> build machinery. Those generated-file hunks can be omitted when
> integrating the change into your development tree.
>
> I would welcome your review of whether this facility belongs in GMP's
> C++ interface, and in particular of the lifetime argument and the
> consuming-source contract.
>
> Kind regards,
>
>    Roberto


More information about the gmp-devel mailing list