C++ Logo

std-proposals

Advanced search

Re: [std-proposals] std::big_int

From: Ell <ell.ell.se_at_[hidden]>
Date: Thu, 02 Apr 2026 14:40:44 +0000
On Thursday, April 2nd, 2026 at 1:43 PM, Jan Schultke via Std-Proposals <std-proposals_at_[hidden]> wrote:

> To be clear, the reason why I am interested in a reference-counting
> implementation is that it solves a lot of programming problems that you
> would otherwise have, such as making a cheap getter that returns by value.
>
> Making temporary copies is simply a common thing in numeric code as well,
> and code is easier to write if you don't need to sweat about the cost of
> negation, absolute values, temporary copies, etc. Implementing a min and max
> function that returns by value also becomes cheap. The load it takes off
> your shoulders is enormous.
>
> Another reason is the open issues with allocation that N4038 ran into. In
> particular, consider the "Rvalue overloads for arithmetic operations" issue:
>
> integer operator+(integer&& lhs, const integer& rhs) {
> return std::move(lsh += rhs);
> }
>
> Overloads that take rvalue references (possibly on either side, resulting
> in four overloads total) are actually quite reasonable because they make it
> possible to reuse allocations, without any effort by the user. The problem
> is that the interface is littered with many more overloads, and this cost
> is not just limited to the standard library but to any library that
> provides some extra numeric functionality for std::big_int. By comparison,
> a reference-counted approach allows the following:
>
> big_int operator+(big_int lhs, big_int rhs) {
> if (lhs.is_big() && rhs.is_big()) {
> // use the allocation with more capacity or something
> }
> else if (lhs.is_big()) {
> if (lhs.is_unique()) {
> __inplace_plus(lhs, rhs);
> return lhs;
> } else {
> return __copying_plus(lhs, rhs);
> }
> }
> else if (rhs.is_big()) {
> // ...
> }
> else {
> return big_int(_BitInt(128)(lhs.small_value) +
> _BitInt(128)
> (rhs.small_value));
> }
> }
>
> This is just to illustrate the principle; the is_unique() check does not
> work with multi-threading; you need to temporarily set the reference
> counter to zero if it is 1, via atomic compare exchange, to obtain
> temporary "unique ownership" over a big_int.
>
> While the implementation of an optimal operator+ is pretty complicated with
> this approach, the point is that the interface is simple, and can be
> improved by implementations gradually without breaking changes. By
> comparison, if std::big_int has unique ownership over its data, you need to
> design the interface around that by potentially adding these rvalue
> overloads, and if you don't, you incur costs everywhere that won't go away
> unless you add more functions to the API.

The problem with a `big_int operator+(big_int lhs, big_int rhs)`
signature is that lvalue arguments cause an unnecessary ref-count
inc+dec, even for basic arithmetic.

Another option is to have a possibly-owning big_int_view type, which
you'd use like `big_int operator+(big_int_view lhs, big_int_view rhs)`.
big_int_view has same layout as big_int, plus an additional "owning"
flag. When initialized from an lvalue (big_int or big_int_view) it
(shallowly) copies the value without taking ownership. When initialized
from an rvalue it copies the value and takes (or inherits) ownership,
clearing the source. This saves you the separate const/rv overloads, and
avoids the extra indirection of a reference.

Received on 2026-04-02 14:40:55