---
title: "A Design Guideline for Scalar Overloads in std::simd"
document: P4276R0
date: 2026-06-15
audience: LEWG
reply-to:
  - "Daniel Towner <daniel.towner@intel.com>"
---

## Introduction

`std::simd` includes scalar overloads for a few operations where variants of a function or operator accept a scalar where a `simd::vec` value would otherwise be expected. Recent papers continue this theme, with better shifts [P3793R1] and funnel shifts [P4010R0] adding scalar-operand variants. This has inadvertently created an expectation that every new operation should provide scalar overloads too, but they are not needed by default. In each case where they exist, they do so for a specific, demonstrable reason, such as access to a distinct hardware instruction.

The reason scalar overloads are not needed by default is that a scalar supplied where a vector is expected is already broadcast to a vector by the converting constructor, and that broadcast always produces the correct result. An explicit scalar overload is therefore only useful when it offers a concrete implementation benefit that the broadcast cannot. This paper proposes a design guideline that a scalar overload should only be provided when there is such a benefit, and otherwise rely on the broadcast that the library already performs.

This question came up doing LWG review of saturating arithmetic for SIMD [P2956R2], but does not propose any changes to that paper.

## Background

The question this guideline answers came up concretely during review of the saturating-arithmetic paper [P2956R2], where it was questioned whether the saturating operations should accept a `simd::vec` together with a scalar, as some other `std::simd` operations do. The question applies far beyond that one paper since it recurs whenever a new data-parallel operation is proposed, so it is worth answering once in general terms, rather than per paper. This paper does so, and does not propose any change to [P2956R2], which has been reviewed and approved by LWG.

## `std::simd` already broadcasts scalars

`std::basic_vec` has a converting constructor from a single value ([simd.ctor]), which allows a scalar argument to be broadcast to every element of the vector. As a direct consequence, an operation defined only for vector operands already accepts a vectorizable scalar in any argument position that participates in conversion, because the scalar is broadcast to a vector first.

For saturating arithmetic the call a user would write already works today, because the scalar is broadcast implicitly by the converting constructor. A dedicated scalar overload would not change what the user writes, nor the result:

<!-- tomd:lossy-table -->

```cpp
simd::vec<int> v = ...;

// resolves to an explicit
// saturating_add(vec, scalar) overload
auto r = std::saturating_add(v, 7);
```

```cpp
simd::vec<int> v = ...;

// 7 is implicitly broadcast to vec<int>,
// then the vec/vec form is called
auto r = std::saturating_add(v, 7);
```

The user writes the same code either way, and it computes the element-wise saturating sum of `v` and a vector of `7`s. There is no separate instruction, no separate semantics, and no observable difference between them. An explicit scalar overload of `saturating_add` would therefore be redundant, while forcing more API surface, more wording, and more tests for no behavioural benefit. This identical-result property is the core of the argument. For an operation whose scalar form is defined as a broadcast, the result is the same by construction, so a scalar overload can never improve correctness. The only thing it could add is an implementation benefit.

## Provide a scalar overload only when there is a benefit

Because broadcasting already produces the correct result, the only justification for a scalar overload is a concrete, demonstrable benefit that the broadcast form cannot achieve, almost always a generated-code benefit. The shift and rotate overloads are the canonical example. A shift by a scalar (or compile-time-constant) count can lower to a shift-by-scalar or shift-by-immediate instruction, whereas a shift by a vector count lowers to a per-element variable-shift instruction. These are genuinely different operations with different cost, so the scalar overload earns its place by giving the user direct access to the cheaper instruction. The result is identical to broadcasting, with the advantage being better generated code.

The shift, rotate, and funnel-shift [P4010] overloads are not a precedent for "scalar parameter overloads everywhere". Instead, they are a precedent for the principle that a scalar overload is added when, and only when, it does something the broadcast cannot. Uniformity or convenience alone is not sufficient.

Saturating arithmetic does not meet this bar. There is no distinct "saturating-add-by-scalar" instruction to reach. The scalar form is defined as the element-wise saturating operation against the broadcast scalar, identical to the all-vector form, and broadcasting is always well-formed. The scalar overload would be redundant with the broadcasting constructor, so it should not be added.

## Proposed guideline

The underlying principle is general: **an overload that merely reproduces what an implicit conversion already does should be added only when it offers a distinct, demonstrable benefit over relying on that conversion.** In `std::simd` the implicit conversion in question is the broadcasting constructor, which gives the following design guideline, proposed for adoption by LEWG as guidance for `std::simd`:

> For `std::simd` functions and operators, a scalar overload (one taking a scalar where a vector operand is otherwise expected) should be provided **only** when it offers a concrete, demonstrable benefit that the broadcasting converting constructor cannot, such as access to a distinct hardware instruction or immediate encoding. Such a benefit should be compelling, and uniformity or convenience alone is not sufficient. Where a scalar argument would merely be broadcast to a vector and produce an identical result, no scalar overload should be added since the broadcasting constructor already provides the functionality.

## Possible polls

Poll: A `std::simd` scalar overload should be added only when it offers a compelling, demonstrable benefit over broadcasting via the converting constructor.

Poll: For [P2956R2] there is no reason to delay its inclusion in the C++29 working draft for lacking scalar overloads.

## References

### Informative References

**[P2956R2]
   Jan Schultke. [Saturation arithmetic for std::simd](https://wg21.link/p2956r2). URL: [https://wg21.link/p2956r2](https://wg21.link/p2956r2)
[P3793R1]
   [Better shifts for std::simd](https://wg21.link/p3793r1). URL: [https://wg21.link/p3793r1](https://wg21.link/p3793r1)
[P4010]
   [Funnel shifts for std::simd](https://wg21.link/p4010). URL: [https://wg21.link/p4010](https://wg21.link/p4010)**
