---
title: "Carry-less product: std::clmul"
document: P3642R6
date: 2026-07-14
audience: LEWG, LWG
reply-to:
  - "Jan Schultke <janschultke@gmail.com>"
---

Add widening and non-widening carry-less multiplication functions.



### Changes since R5

> The paper was forwarded from LEWG to LWG during the 2026-06 Brno meeting, but got stuck in LWG due to some design concerns that require LEWG input.

As requested by LEWG during the 2026 Brno meeting:

- Replace the bump of `__cpp_lib_simd` with a new `__cpp_lib_simd_clmul`
- Constrain `std::wide_result` to unsigned integer types

Also, following LWG review during the 2026 Brno meeting:

- Apply various wording adjustments during review
- Add §6. Potential design changes following LWG review
- Add wording for those design changes in §8. Optional wording changes A and §9. Optional wording changes B

Furthermore, some editorial changes:

- Mention in §4.1. Intrinsic implementation that `__builtin_elementwise_clmul` is available in Clang 23
- Expand §4.1. Intrinsic implementation with possible optimizations

### Changes since R4

- Rename `std::clmul_wide` to `std::widening_clmul`
- Fix `std::simd::clmul` specified as a unary function (should be binary)
- Rebase §7. Proposed wording on [[N5032]](https://wg21%2elink/n5032) with post-Croydon changes applied

### Changes since R3

- Reference faster possible implementations in §4. Possible implementation
- Add some thoughts on why implementations in the compiler are better than pure library implementations
- Mention that `@llvm.clmul` has now been added to LLVM ([[LLVMClmul]](https://llvm%2eorg/docs/LangRef%2ehtml#llvm-clmul-intrinsic))
- Change `operator<=>` for `wide_result` to be a hidden friend again

### Changes since R2

- Add missing `<T>` for `operator<=>`
- Provide a §4. Possible implementation better suited for SIMD parallelization
- Rebase §7. Proposed wording on N5014
- Improve §7. Proposed wording editorially

### Changes since R1

The paper was seen by SG6 at Sofia 2025 with the following feedback:

> **Summary**: SG6 had no numerics concerns but recommended to include std::simd overloads into the paper.
> 
> **POLL**: Forward P3642R1 to LEWG with the expectation that the next revision includes `std::simd` overloads.
> 
> | SF | F | N | A | SA |
> | --- | --- | --- | --- | --- |
> | 7 | 4 | 0 | 0 | 0 |

The following changes were made:

- Provide (non-widening) §5.3. SIMD support
- Use two-space indentation, and generally match the code style of the C++ standard
- Provide detailed design description for §5.2. Widening operation
- Make §7. Proposed wording and design independent of [[P3161R4]](https://wg21%2elink/p3161r4)
- Fix stray `U` type name in §7. Proposed wording, and improve wording generally
- Rebase §7. Proposed wording on `std::simd` naming changes in [[P3691R1]](https://wg21%2elink/p3691r1)

### Changes since R0

- Generate the proposal using COWEL instead of bikeshed
- Fix incorrect formula in §7. Proposed wording for bits ≥ the integer width
- Fix §4.2. Hardware support missing new VPCLMULQDQ instructions
- Fix improper uses of `std::unsigned_integral` in §2. Introduction
- Make slight editorial wording adjustments
- Rebase on N5008 and [[P3161R4]](https://wg21%2elink/p3161r4)
- Mention [[SimdJsonClmul]](https://branchfree%2eorg/2019/03/06/code-fragment-finding-quote-pairs-with-carry-less-multiply-pclmulqdq/) in §3. Motivation

## Introduction

[Carry-less multiplication](https://en.wikipedia.org/wiki/Carry-less_product) is a simple numerical operation on unsigned integers. It can be a seen as a regular multiplication where `xor` is being used as a reduction instead of `+`.

It is also known as "XOR multiplication" and "polynomial multiplication". The latter name is used because mathematically, it is equivalent to performing a multiplication of two polynomials in GF(2), where each bit is a coefficient.

I propose a `std::clmul` function to perform this operation:

```cpp
template<class T>
constexpr T clmul(T x, T y) noexcept;
```

I also propose a widening operation in the style of [[P3161R4]](https://wg21%2elink/p3161r4), as follows:

```cpp
template<class T>
struct wide_result {
T low_bits;
T high_bits;
// ...
};
template<class T>
constexpr wide_result<T> widening_clmul(T x, T y) noexcept;
```

## Motivation

Carry-less multiplication is an important operation in a number of use cases:

- **CRC Computation:** While cyclic redundancy checks can theoretically be performed with a finite field of any length, in practice, [GF(2)[X]](https://en.wikipedia.org/wiki/GF(2)), the *polynomial ring* over the *Galois field* with two elements is used. Polynomial addition in this ring can be implemented via `xor`, and multiplication via `clmul`, which makes cyclic redundancy checks considerably faster.
- **Cryptography:** `clmul` may be used to implement AES-GCM. [[IntelClmul]](https://www%2eintel%2ecom/content/dam/develop/external/us/en/documents/clmul-wp-rev-2-02-2014-04-20%2epdf) describes this process in great detail and motivates hardware support for carry-less multiplication via the `pclmulqdq` instruction.
- **Bit manipulation:** `clmul` performs a large amount of `<<` and `xor` operations in parallel. This is utilized in the reference implementation [[BitPermutations]](https://github%2ecom/Eisenwave/cxx26-bit-permutations) of `std::bit_compressr`, proposed in [[P3104R3]](https://wg21%2elink/p3104r3). For example, the form `clmul(x, -1u)` computes the bitwise inclusive parity for each bit of `x` and the bits to its right.

Carry-less multiplication is of such great utility that there is widespread hardware support, some dating back more than a decade. See below for motivating examples.

### Parity computation and JSON parsing

The parity of an integer `x` is `0` if the number of one-bits is even, and `1` if it is odd. The parity can also be computed with `popcount(x) & 1`.

> The special form `clmul(x, -1)` computes the parity of each bit in `x` and the bits to its right. The most significant bit holds the parity of `x` as a whole.
> 
> ```cpp
> bool parity(std::uint32_t x) {
> return std::clmul(x, -1u) >> 31;
> }
> ```

While the parity of *all* bits can be obtained with `clmul`, it computes the inclusive cumulative parity, which can be used to accelerate parsing JSON and other file formats ([[SimdJsonClmul]](https://branchfree%2eorg/2019/03/06/code-fragment-finding-quote-pairs-with-carry-less-multiply-pclmulqdq/)). This can be done by mapping each `"` character onto a `1`-bit, and any other character onto `0`. `clmul(x, -1)` would then produce masks where string characters corresponds to a `1`-bit.

> ```
> abc xxx "foobar" zzz "a" // input string
> 000000001000000100000101 // quote_mask
> 00000000.111111.00000.1. // clmul(quote_mask, -1), ignoring 1-bits of quote_mask
> ```

### Fast space-filling curves

The special form `clmul(x, -1)` can be used to accelerate the computation of Hilbert curves. To properly understand this example, I will explain the basic notion of space-filling curves.

We can fill space using a 2D curve by mapping the index `i` on the curve onto Cartesian coordinates `x` and `y`. A naive curve that fills a 4x4 square can be computed as follows:

```cpp
struct pos { uint32_t x, y; };
pos naive_curve(uint32_t i) { return { i % 4, i / 4 }; }
```

When mapping the index `i = 0, 1, ..., 0xf` onto the returned 2D coordinates, we obtain the following pattern:

```
0 1 2 3
4 5 6 7
8 9 a b
c d e f
```

The problem with such a naive curve is that adjacent indices can be positioned very far apart (the distance increases with row length). For image processing, if we store pixels in this pattern, cache locality is bad; two adjacent pixels can be very far apart in memory.

A [Hilbert curve](https://en.wikipedia.org/wiki/Hilbert_curve) is a family of space-filling curves where the distance between two adjacent elements is `1`:

```
0 1 e f
3 2 d c
4 7 8 b
5 6 9 a
```

De-interleaving bits of `i` into `x` and `y` yields a [Z-order curve](https://en.wikipedia.org/wiki/Z-order_curve), and performing further transformations yields a [Hilbert curve](https://en.wikipedia.org/wiki/Hilbert_curve).

> `clmul` can be used to compute the bitwise parity for each bit and the bits to its right, which is helpful for computing Hilbert curves. Note that the following example uses the `std::bit_compress` function from [[P3104R3]](https://wg21%2elink/p3104r3), which may also be accelerated using `clmul`.
> 
> ```cpp
> pos hilbert_to_xy(uint32_t i) {
> // De-interleave the bits of i.
> uint32_t i0 = std::bit_compress(i, 0x55555555u); // abcdefgh → bdfh
> uint32_t i1 = std::bit_compress(i, 0xaaaaaaaau); // abcdefgh → aceg
> // Undo the permutation that Hilbert curves apply on top of Z-order curves.
> uint32_t A = i0 & i1;
> uint32_t B = i0 ^ i1 ^ 0xffffu;
> uint32_t C = std::clmul(A, -1u) >> 16;
> uint32_t D = std::clmul(B, -1u) >> 16;
> uint32_t a = C ^ (i0 & D);
> return { .x = a ^ i1, .y = a ^ i0 ^ i1 };
> }
> ```
> 
> This specific example is taken from [FastHilbertCurves]. [[HackersDelight]](https://doc%2elagout%2eorg/security/Hackers'Delight%2epdf) explains the basis behind this computation of Hilbert curves using bitwise operations.

When working with space-filling curves, the inverse operation is also common: mapping the Cartesian coordinates onto an index on the curve. In the case of Z-order curves aka. Morton curves, this can be done by simply interleaving the bits of `x` and `y`. A Z-order curve is laid out as follows:

```
0 1 4 5
2 3 6 7
8 9 c d
a b e f
```

> `clmul` can be used to implement bit-interleaving in order to generate a [Z-order curves](https://en.wikipedia.org/wiki/Z-order_curve).
> 
> ```cpp
> uint32_t xy_to_morton(uint32_t x, uint32_t y) {
> uint32_t lo = std::clmul(x, x) << 0; // abcd -> 0a0b0c0d
> uint32_t hi = std::clmul(y, y) << 1; // abcd -> a0b0c0d0
> return hi | lo;
> }
> ```

> In the example above, `std::clmul(x, x)` is equivalent to [[P3104R3]](https://wg21%2elink/p3104r3)'s `std::bit_expand(x, 0x55555555u)`.

## Possible implementation

A naive and unconstrained implementation looks as follows:

```cpp
template<class T>
constexpr T clmul(const T x, const T y) noexcept {
T result = 0;
for (int i = 0; i < numeric_limits<T>::digits; ++i) {
result ^= x * (y & (T{1} << i));
}
return result;
}
```

> This implementation is particularly suited for auto-vectorization. Assuming the loop is unrolled, `T{1} << i` is constant and each bitwise AND and multiplication can be done in parallel. Lastly, all results have to be accumulated using a horizontal XOR.
> 
> Expressed in `std::simd` terms, this looks something like:
> 
> ```cpp
> using V = simd::vec<T, numeric_limits<T>::digits>;
> static constexpr V powers = array<T, V::size()>{ 0, 1, 2, 4, 8, /* ... */ };
> return simd::reduce(V(x) * (V(y) & powers), bit_xor<T>{});
> ```

Such a naive implementation is far from optimal though. [[QuickBench]](https://quick-bench%2ecom/q/eG4Q5BR_udnfh4V5f-3d3h3fRHY) shows that a naive `clmul` implementation which computes both the high and the low bits performs 9.2× worse than an efficient implementation taken from [[NTL]](https://github%2ecom/libntl/ntl).

> The mathematical basis for the [[NTL]](https://github%2ecom/libntl/ntl) implementation is described in [[FasterMultiplicationInGF2]](https://inria%2ehal%2escience/inria-00188261v4/document).

### Intrinsic implementation

Since Clang 23, LLVM also provides a portable `@llvm.clmul` intrinsic function ([[LLVMClmul]](https://llvm%2eorg/docs/LangRef%2ehtml#llvm-clmul-intrinsic)), and Clang provides a `__builtin_elementwise_clmul` intrinsic. Thus, the operation can be lowered like: `std::clmul` → `__builtin_clmul` → `@llvm.clmul` → `pclmulqdq`.

While a pure library implementation of `std::clmul` is possible, it misses out on many optimization opportunities.

> - `std::clmul` is commutative, pure, re-entrant, non-throwing, does not affect the floating-point environment, etc. Not all of these properties can be easily communicated for a library function.
> - Carry-less multiplication with `0` or `1` can be simplified irrespective of the other factor.
> - If one factor has a large number of trailing zeroes, the operation can be simplified by shifting that factor to the right, performing the multiplication, and shifting back to the left. This may avoid multiprecision operations; that is, it can turn a 128-bit multiplication into a 64-bit multiplication.
> - If one factor is known to be a power of two, even if the exact value isn't known, normal multiplication can be used, which may be faster.
> - If one factor is a mask (that is, a sequence of contiguous one-bits), carry-less multiplication is equivalent to a parallel prefix XOR or bitwise parity operation, which is simpler than the usual software implementation.
> - `std::clmul(x, x)` is equivalent to interleaving bits of `x` with zeroes. This pattern of squaring can be recognized (even if the value of `x` is unknown) and transformed into `std::bit_expand(x, 0x5555'5555u)` if the `PDEP` instruction is available in hardware. On x86_64, this may be beneficial because carry-less multiplication is a vector instruction and `PDEP` may have less overhead.

### Hardware support

The implementation difficulty lies mostly in utilizing available hardware instructions, not in the naive fallback implementation.

In the following table, let `uN` denote `N`-bit unsigned integer operands, and `×N` denote the amount of operands that are processed in parallel.

| Operation | x86_64 | ARM | RV64 |
| --- | --- | --- | --- |
| `clmul u64×4 → u128×4` | `vpclmulqdq`<sup>VPCLMULQDQ</sup> |  |  |
| `clmul u64×2 → u128×2` | `vpclmulqdq`<sup>VPCLMULQDQ</sup> |  |  |
| `clmul u64 → u128` | `pclmulqdq`<sup>PCLMULQDQ</sup> | `pmull`+`pmull2`<sup>Neon</sup> | `clmul`+`clmulh`<sup>Zbc, Zbkc</sup> |
| `clmul u64 → u128` | `pclmulqdq`<sup>PCLMULQDQ</sup> | `pmull`+`pmull2`<sup>Neon</sup> | `clmul`+`clmulh`<sup>Zbc, Zbkc</sup> |
| `clmul u64 → u64` |  | `pmull`<sup>Neon</sup> | `clmul`<sup>Zbc, Zbkc</sup> |
| `clmul u8×8 → u16×8` |  | `pmull`<sup>Neon</sup> |  |
| `clmul u8×8 → u8×8` |  | `pmul`<sup>Neon</sup> |  |

> A limited x86_64 implementation of `widening_clmul` may look as follows:
> 
> ```cpp
> #include <immintrin.h>
> #include <cstdint>
> wide_result<uint64_t> widening_clmul(uint64_t x, uint64_t y) noexcept {
> __m128i x_128 = _mm_set_epi64x(0, x);
> __m128i y_128 = _mm_set_epi64x(0, y);
> __m128i result_128 = _mm_clmulepi64_si128(x_128, y_128, 0);
> return {
> .low_bits = uint64_t(_mm_extract_epi64(result_128, 0)),
> .high_bits = uint64_t(_mm_extract_epi64(result_128, 1))
> };
> }
> ```

## Design considerations

Multiple design choices lean on [[P0543R3]](https://wg21%2elink/p0543r3) and [[P3161R4]](https://wg21%2elink/p3161r4). Specifically,

- the choice of header `<numeric>`,
- the choice to have a widening operation,
- the `_wide` naming scheme,
- the `wide_result` template, and
- the decision to have a `(T, T)` parameter list.

### Naming

Carry-less multiplication is also commonly called "Galois Field Multiplication" or "Polynomial Multiplication".

The name `clmul` was chosen because it carries no domain-specific connotation, and because it is widespread:

- Intel refers to `PCLMULQDQ` As "Carry-Less Multiplication Quadword" in its manual; see [[IntelManual]](https://software%2eintel%2ecom/en-us/download/intel-64-and-ia-32-architectures-sdm-combined-volumes-1-2a-2b-2c-2d-3a-3b-3c-3d-and-4).
- RISC-V refers to `clmul` as carry-less multiplication, and this is obvious from the mnemonic.
- The Wikipedia article ([[WikipediaClmul]](https://en%2ewikipedia%2eorg/wiki/Carry-less_product)) for this operation is titled "Carry-less product".
- The portable LLVM intrinsic function ([[LLVMClmul]](https://llvm%2eorg/docs/LangRef%2ehtml#llvm-clmul-intrinsic)) is named `@llvm.clmul`.

### Widening operation

In addition to the `std::clmul` function template, there exists a `std::widening_clmul` function template:

```cpp
template<class T>
struct wide_result {
T low_bits;
T high_bits;
friend constexpr bool operator==(const wide_result&, const wide_result&) = default;
friend constexpr strong_ordering operator<=>(const wide_result& x, const wide_result& y)
noexcept;
};
template<class T>
constexpr wide_result<T> widening_clmul(T x, T y) noexcept;
```

Such a widening function is important in a various cryptographic use cases. There is universal §4.2. Hardware support for obtaining all 128 bits of a multiplication for that reason.

Most of the design choices take the design of [[P3161R4]](https://wg21%2elink/p3161r4) and [[P4052R0]](https://wg21%2elink/p4052r0) into consideration:

- The function is named `widening_clmul` to be symmetrical with `std::saturating_mul` from [[P4052R0]](https://wg21%2elink/p4052r0) (merged into C++26). That proposal broadly advocated for a naming scheme leaning on Rust, and the Rust standard library has a `widening_mul` function.
- The result type is deliberately not named `wide_clmul_result` so that future `widening_mul` and other operations can use the same result type, which avoids creating an ever-growing set of equivalent (but distinct) types.
- The `low_bits` appear before the `high_bits`, so that on the more widespread little-endian architectures, the layout of the `struct` is identical to that of an integer, which is slightly better for calling conventions.

However, the comparison operators are a novel invention of this proposal. They are intended to behave as if the comparisons were performed on an integer with twice the width of `T`. These comparisons exists so that the result can be easily compared against expected results in test cases, stored in containers like `std::set`, used out of the box with `std::sort`, etc. There is an obvious and mathematically meaningful ordering of `wide_result`s, so it would be strange not to add a comparison operator.

Also, `wide_result` should be a broadly useful vocabulary type which may be instantiated with user-defined numeric types, simply because that seems like a useful side product of this proposal.

> It would not be possible to simply return an integer with twice the width of the input because it is not guaranteed that such a type exists, especially in the case of `unsigned long long` inputs.

### SIMD support

Upon seeing this proposal at Sofia 2025, SG6 recommended to add SIMD support. This recommendation was provided under the assumption that it would be a simple addition in the style of [[P2933R4]](https://wg21%2elink/p2933r4). Therefore, this proposal provides non-widening SIMD carry-less multiplication with the following signature:

```cpp
template<simd-integral V>
constexpr V clmul(const V& a, const V& b) noexcept;
```

#### SIMD widening operations are out of scope

AVX-512 provides a `u64×4 → u128×4` operation, and there is currently no precedent for such widening operations in the SIMD library. Specifically, the `VPCLMULQDQ` instruction ignores one of each `u64×2` pairs, and produces a 128-bit output for each such pair.

It would take *considerable* design and wording effort to standardize this, especially if one wants to expose the full `VPCLMULQDQ` behavior, which allows choosing for each `u64×2` integer pair, which of these integers is multiplied and which is ignored. Procedurally, that design effort should be part of [[P3161R4]](https://wg21%2elink/p3161r4) (which proposes widening operations in general) or some follow-up proposal for SIMD widening operations. Some other SIMD instructions like `PMULUDQ` perform multiple widening multiplications in parallel, in the same style as `VPCLMULQDQ`, while some others compute just the upper bits, like `VPMULHUW`. This is a broad design space.

In conclusion, a proposal for widening SIMD operations *in general* would be well-motivated. For `std::clmul`, designing SIMD widening operations would be scope creep.

## Potential design changes following LWG review

> The design in the preceding section is what has been approved by LEWG and what is reflected in §7. Proposed wording. This section discusses potentional additioal changes.

During LWG review, some minor wording changes were fixed. In addition, certain design questions have been raised during LWG review of the paper:

### Aggregate type std::wide_result

Should `std::wide_result` actually be an aggregate type? A notable problem is that this permits structured bindings and list initialization, and the low bits first order was perceived as more surprising than the other way around. Furthermore, while big-endian architectures are uncommon, they do exist, and `std::wide_result` is less friendly in its layout to those. To solve this:

- The type could be converted to a non-aggregate, with member functions for accessing the data members rather than making them public.
- Structured bindings access could simply not be provided, which would prevent the potential resulting bugs.

The wording for this option is provided in §8. Optional wording changes A.

### Alignment of std::wide_result<std::uint64_t>

Should `std::wide_result<std::uint64_t>` be required to be over-aligned to 128 bits? This would be consistent with `__int128` but inconsistent with `_BitInt(128)`; the latter is only aligned to 64 bits.

I personally believe that the standard should not mandate either one. Both options are plausible ipmlementation choices, and if the neither the C nor the C++ standard mandates specific alignment for integer types, why would we mandate it here? Mandating 128-bit alignment for `std::wide_result<std::uint64_t>` also suggests that several ABIs have made a mistake by under-aligning `_BitInt(128)`, which is not a claim we should be making without *extremely* strong evidence and ABI expertise.

> No wording for such a requirement is provided in this paper.

### Integer interoperability

While `std::wide_result<std::uint32_t>` is strongly implied to have the same layout as `std::uint64_t`, and while this is desired, it's not actually guaranteed. There also seems to be no good reason why that guarantee wouldn't be possible to provide, at least if `std::wide_result` is not an aggregate and the implementation has some more freedom regarding its layout.

That is, we could guarantee that `std::wide_result<_BitInt(N)> has the same layout as `_BitInt(N * 2)`` (though phrased generally, not just for bit-precise integers).

The wording for this option is provided in §9. Optional wording changes B.

## Proposed wording

The proposed changes are relative to [[N5032]](https://wg21%2elink/n5032), with the changes accepted during the 2026-02 Croydon meeting applied.

### [version.syn]

Add feature test macros to [[version.syn] paragraph 2](https://eel.is/c++draft/version.syn#2) as follows:

```cpp
[…]
#define __cpp_lib_clmul 20????L // also in <numeric>
[…]
#define __cpp_lib_simd_clmul 20????L // also in <simd>
```

### [numeric.ops]

Add the following declarations to the synopsis in [[numeric.ops.overview]](https://eel.is/c++draft/numeric.ops.overview), immediately following the declarations associated with [[numeric.sat]](https://eel.is/c++draft/numeric.sat):

```cpp
// [numeric.clmul], carry-less product
template<class T>
struct wide_result {
T low_bits;
T high_bits;
friend constexpr bool operator==(const wide_result&, const wide_result&) = default;
friend constexpr strong_ordering operator<=>(const wide_result& x, const wide_result& y)
noexcept;
};
template<class T>
constexpr wide_result<T> widening_clmul(T x, T y) noexcept;
template<class T>
constexpr T clmul(T x, T y) noexcept;
```

In subclause [[numeric.ops]](https://eel.is/c++draft/numeric.ops), append a subclause immediately following [[numeric.sat]](https://eel.is/c++draft/numeric.sat):

### Carry-less product [numeric.clmul]

1 A specialization `wide_result<T>` is ill-formed unless `T` is an unsigned integer type ([[basic.fundamental]](https://eel.is/c++draft/basic.fundamental)).

```cpp
constexpr strong_ordering operator<=>(const wide_result& x, const wide_result& y)
noexcept;
```

2 *Returns*: `tie(x.high_bits, x.low_bits) <=> tie(y.high_bits, y.low_bits)`.

```cpp
template<class T>
constexpr wide_result<T> widening_clmul(T x, T y) noexcept;
```

3 Let:

- ⨁ be a reduction using the exclusive OR operation ([[expr.xor]](https://eel.is/c++draft/expr.xor));
- for an integer α, αi be the ith least significant bit in the base-2 representation of α;
- N be the width of `T`.

4 *Constraints*: `T` is an unsigned integer type ([[basic.fundamental]](https://eel.is/c++draft/basic.fundamental)).

5 *Returns*: A `wide_result<T>` object storing the bits of an integer c of width 2N, where the value of ci is given by Formula `?.?`, x is `x`, and y is `y`. The result object is initialized so that

- `low_bits` stores the N least significant bits of c, and
- `high_bits` stores the subsequent N bits of c.

[FORMULA ?.?]

c

i

=

⨁

j

=

0

i

x

j

y

i

−

j

```cpp
template<class T>
constexpr T clmul(T x, T y) noexcept;
```

6 *Constraints*: `T` is an unsigned integer type ([[basic.fundamental]](https://eel.is/c++draft/basic.fundamental)).

7 *Returns*: `widening_clmul(x, y).low_bits`.

If the mathematical notation in the block above does not render for you, you are using an old browser with no MathML support. Please open the document in a recent version of Firefox or Chrome.

> The formula is taken from [[IntelClmul]](https://www%2eintel%2ecom/content/dam/develop/external/us/en/documents/clmul-wp-rev-2-02-2014-04-20%2epdf), with different variable names, and with no special case for the upper N bits; we can simply treat the integers as mathematical integers with 2N width.
> 
> See [[iterator.concept.wine]](https://eel.is/c++draft/iterator.concept.wine) for precedent on using N to denote the width of a type.
> 
> See [[sf.cmath.riemann.zeta]](https://eel.is/c++draft/sf.cmath.riemann.zeta) for precedent on wording which includes formulae.

> The formula above in TeX notation is:
> 
> ```cpp
> c_i = \bigoplus_{j = 0}^i x_i y_{i - j}
> ```

### [simd]

Add the following declarations to the synopsis in [[simd.syn]](https://eel.is/c++draft/simd.syn):

```cpp
namespace std::simd {
  […]

 // [simd.clmul], carry-less product
template<simd-integral V> constexpr V clmul(const V& a, const V& b) noexcept;
// [simd.math], mathematical functions
template<math-floating-point V> constexpr deduced-simd-t<V> acos(const V& x);
  […]
}
```

In subclause [[simd]](https://eel.is/c++draft/simd), append a subclause immediately preceding [[simd.math]](https://eel.is/c++draft/simd.math):

### Carry-less product [simd.clmul]

```cpp
template<simd-integral V> constexpr V clmul(const V& a, const V& b) noexcept;
```

1 *Constraints*: The type `V::value_type` is an unsigned integer type ([[basic.fundamental]](https://eel.is/c++draft/basic.fundamental)).

2 *Returns*: A `basic_vec` object where the ith element is initialized to the result of `std::clmul(a[i], b[i])` ([[numeric.clmul]](https://eel.is/c++draft/numeric.clmul)) for all i in the range [`0`, `V::size()`).

## Optional wording changes A

Optionally, if `std::wide_result` is not to be an aggregate type, immediately apply the following changes on top of the preceding changes.

Change the declaration of `std::wide_result` in [[numeric.ops.overview]](https://eel.is/c++draft/numeric.ops.overview) as follows:

```cpp
template<class T>
struct class wide_result {
T low_bits;
T high_bits;
public:
constexpr wide_result(T high, T low) noexcept
: high-bits(high), low-bits(low) {}
constexpr T high_bits() const noexcept { return high-bits; }
constexpr T low_bits() const noexcept { return low-bits; }
friend constexpr bool operator==(const wide_result&, const wide_result&) = default;
friend constexpr strong_ordering operator<=>(const wide_result& x, const wide_result& y)
noexcept;
private:
T high-bits, low-bits; // exposition-only
};
```

Change [[numeric.clmul]](https://eel.is/c++draft/numeric.clmul) as follows:

1 A specialization `wide_result<T>` is ill-formed unless `T` is an unsigned integer type ([[basic.fundamental]](https://eel.is/c++draft/basic.fundamental)).

<ins> 2 A specialization of `wide_result` is a standard-layout, trivially copyable class type. The members `*high-bits*` and `*low-bits*` of any specialization of `wide_result` may be reordered. </ins>

<ins> 3 *Recommended practice*: The member `*low-bits*` should precede the member `*high-bits*` in all specializations of `wide_result` if `endian::native` equals `endian::little` ([[bit.endian]](https://eel.is/c++draft/bit.endian)). </ins>

**Bulk edit**: In the remainder of [[numeric.clmul]](https://eel.is/c++draft/numeric.clmul), replace any mention of <del>`high_bits`</del> with <ins>`*high-bits*`</ins> and any mention of <del>`low_bits`</del> with <ins>`*low-bits*`</ins>.

## Optional wording changes B

Optionally, if we want to provide stricter guarantees regarding integer layout compatibility, immediately apply the following changes on top of the preceding changes.

If the changes from §8. Optional wording changes A have been applied, delete the *Recommended practice* element in [[numeric.clmul]](https://eel.is/c++draft/numeric.clmul):

*Recommended practice*: The member `*low-bits*` should precede the member `*high-bits*` in all specializations of `wide_result` if `endian::native` equals `endian::little` ([[bit.endian]](https://eel.is/c++draft/bit.endian)).

In any case, insert a new paragraph immediately preceding the library declarations in [[numeric.clmul]](https://eel.is/c++draft/numeric.clmul):

For two objects w0 and w1 of a type `wide_result<T>`, if `T` has no padding bits and there exists an unsigned integer type `W` with twice the width of `T` and with no padding bits, then `w0 <=> w1` shall be equivalent to `bit_cast<W>(w0) <=> bit_cast<W>(w1)` and `wide_result<T>` shall have the same size and alignment as `W`.

## References

[N5032]

Thomas Köppe.

Working Draft Programming Languages — C++

2025-12-15

https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2025/n5032.pdf

[P0543R3]

Jens Maurer.

Saturation arithmetic

2023-07-19

https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2023/p0543r3.html

[P3104R3]

Jan Schultke.

Bit permutations

2025-02-11

https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2025/p3104r3.html

[P3161R4]

Tiago Freire.

Unified integer overflow arithmetic

2025-03-26

https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2025/p3161r4.html

[P2933R4]

Daniel Towner, Ruslan Arutyunyan.

Extend <bit> header function with overloads for std::simd

2025-02-13

https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2025/p2933r4.html

[P3691R1]

Matthias Kretz et al..

Reconsider naming of the namespace for "std::simd"

2025-06-17

https://wg21.link/p3691r1

[P4052R0]

Jan Schultke, Corentin Jabot.

Renaming saturation arithmetic functions

2025-03-13

https://wg21.link/p4052r0

[BitPermutations]

Jan Schultke.

C++26 Bit permutations reference implementation

https://github.com/Eisenwave/cxx26-bit-permutations

[SimdJsonClmul]

Geoff Langdale.

Code Fragment: Finding quote pairs with carry-less multiply (PCLMULQD)

2019-03-06

https://branchfree.org/2019/03/06/code-fragment-finding-quote-pairs-with-carry-less-multiply-pclmulqdq/

[IntelClmul]

Shay Gueron, Michael E. Kounavis.

Intel® Carry-Less Multiplication Instruction and its Usage for Computing the GCM Mode

https://www.intel.com/content/dam/develop/external/us/en/documents/clmul-wp-rev-2-02-2014-04-20.pdf

[IntelManual]

Intel Corporation.

Intel® 64 and IA-32 Architectures Software Developer's Manual

https://software.intel.com/en-us/download/intel-64-and-ia-32-architectures-sdm-combined-volumes-1-2a-2b-2c-2d-3a-3b-3c-3d-and-4

[HackersDelight]

Henry S. Warren, Jr.

Hacker's Delight, Second Edition

https://doc.lagout.org/security/Hackers'Delight.pdf

[FastHilbertCurves]

rawrunprotected.

2D Hilbert curves in O(1)

"http://threadlocalmutex.com/?p=188

[WikipediaClmul]

Wikipedia community.

Carry-less product

https://en.wikipedia.org/wiki/Carry-less_product

[LLVMClmul]

Documentation for 'llvm.clmul.*' Intrinsic

https://llvm.org/docs/LangRef.html#llvm-clmul-intrinsic

[QuickBench]

Benchmark of naive clmul vs. NTL clmul

https://quick-bench.com/q/eG4Q5BR_udnfh4V5f-3d3h3fRHY

[NTL]

Victor Shoup.

NTL GitHub repository

https://github.com/libntl/ntl

[FasterMultiplicationInGF2]

Richard P. Brent, Pierrick Gaudry, Emmanuel Thomé, Paul Zimmermann.

Faster Multiplication in GF(2)[x]

2008-11-07

https://inria.hal.science/inria-00188261v4/document
