This page is a snapshot from the LWG issues list, see the Library Active Issues List for more information and the meaning of New status.
<stdfloat> typesSection: 29.10 [simd] Status: New Submitter: Matthias Kretz Opened: 2025-10-15 Last modified: 2025-10-23
Priority: 1
View other active issues in [simd].
View all other issues in [simd].
View all issues with New status.
Discussion:
Addresses DE-288 and DE-285
29.10.8.7 [simd.loadstore]unchecked_store and partial_store are constrained with
indirectly_writable in such a way that basic_vec's value_type must satisfy
convertible_to<range value-type>. But that is not the case,
e.g. for float → float16_t or double → float32_t. However,
if simd::flag_convert is passed, these conversions were intended to work. The
implementation thus must static_cast the basic_vec values to the range's value-type
before storing to the range.
unchecked_store(vec<float>, span<complex<float16_t>>, flag_convert)
does not work for a different reason. The complex(const float16_t&, const float16_t&)
constructor simply does not allow construction from float, irrespective of
using implicit or explicit conversion. The only valid conversion from float →
complex<float16_t> is via an extra step through complex<float16_t>::value_type.
This issue considers it a defect of complex that an explicit conversion from
float → complex<float16_t> is ill-formed and therefore no workaround/special
case is introduced.
Conversely, the conversion constructor in 29.10.7.2 [simd.ctor] does not reject
conversion from vec<complex<float>, 4> to vec<float, 4>.
I.e. convertible_to<vec<complex<float>, 4>, vec<float, 4>>
is true, which is a lie. This is NB comment DE-288. However, the NB comment's proposed
resolution is too strict, in that it would disallow conversion from float to float16_t.
The conversion/load from static-sized range constructor in 29.10.7.2 [simd.ctor] has a
similar problem:
convertible_to<array<std::string, 4>, vec<int, 4>>istrue
but when fixing this
vec<float16_t, 4>(array<float, 4>, flag_convert)
must continue to be valid.
unchecked_load and partial_load in 29.10.8.7 [simd.loadstore] currently Mandate
the range's value-type to be vectorizable, but converting loads from complex<float>
to float are not covered. It is unclear what a conversion from complex<float>
to float should do, so it needs to be added (again without breaking float → float16_t).
29.10.8.11 [simd.permute.memory] is analogous to 29.10.8.7 [simd.loadstore] and needs
equivalent constraints.
29.10.7.2 [simd.ctor] p2 requires constructible_from<U>, which makes explicit
construction of vec<float16_t> from float ill-formed. For consistency this
should also be constrained with explicitly-convertible-to.
[2025-10-22; Reflector poll.]
Set priority to 1 after reflector poll.
We also need to update Effects. There are more places in 29.10 [simd]
where float to float16_t and similar conversion are not supported.
It was pointed out that similar issues happen for complex<float16_t>.
There seem to be mismatch between language initialization rules and the intended
usage based on library API.
Previous resolution [SUPERSEDED]:
This wording is relative to N5014.
In 29.10.3 [simd.syn] and 29.10.8.7 [simd.loadstore] replace all occurrences of
indirectly_writable<ranges::iterator_t<R>, T>with
indirectly_writable<ranges::iterator_t<R>,Tranges::range_value_t<R>>and all occurrences of
indirectly_writable<I, T>with
indirectly_writable<I,Titer_value_t<I>>Modify 29.10.8.7 [simd.loadstore] as indicated:
template<class T, class Abi, ranges::contiguous_range R, class... Flags> requires ranges::sized_range<R> && indirectly_writable<ranges::iterator_t<R>, T> constexpr void unchecked_store(const basic_vec<T, Abi>& v, R&& r, flags<Flags...> f = {}); […] template<class T, class Abi, contiguous_iterator I, sized_sentinel_for<I> S, class... Flags> requires indirectly_writable<I, T> constexpr void unchecked_store(const basic_vec<T, Abi>& v, I first, S last, const typename basic_vec<T, Abi>::mask_type& mask, flags<Flags...> f = {});-11- Let […]
-?- Constraints: The expressionstatic_cast<ranges::range_value_t<R>>(x)wherexis an object of typeTis well-formed. -12- Mandates: Ifranges::size(r)is a constant expression thenranges::size(r) ≥ simd-size-v<T, Abi>. […]template<class T, class Abi, ranges::contiguous_range R, class... Flags> requires ranges::sized_range<R> && indirectly_writable<ranges::iterator_t<R>, T> constexpr void partial_store(const basic_vec<T, Abi>& v, R&& r, flags<Flags...> f = {}); […] template<class T, class Abi, contiguous_iterator I, sized_sentinel_for<I> S, class... Flags> requires indirectly_writable<I, T> constexpr void partial_store(const basic_vec<T, Abi>& v, I first, S last, const typename basic_vec<T, Abi>::mask_type& mask, flags<Flags...> f = {});-15- Let […]
-?- Constraints: The expressionstatic_cast<iter_value_t<I>>(x)wherexis an object of typeTis well-formed. -16- Mandates: […]
[2025-10-22; Matthias Kretz improves discussion and provides new wording]
Proposed resolution:
This wording is relative to N5014.
Modify 29.10.2 [simd.expos] as indicated:
[…]
template<class T>
concept constexpr-wrapper-like = // exposition only
[…]
bool_constant<static_cast<decltype(T::value)>(T()) == T::value>::value;
template<class From, class To>
concept explicitly-convertible-to = // exposition-only
requires {
static_cast<To>(declval<From>());
};
template<class T> using deduced-vec-t = see below; // exposition only
[…]
Modify 29.10.3 [simd.syn] as indicated:
[…] template<class T, class Abi, ranges::contiguous_range R, class... Flags> requires ranges::sized_range<R>&& indirectly_writable<ranges::iterator_t<R>, T>constexpr void unchecked_store(const basic_vec<T, Abi>& v, R&& r, flags<Flags...> f = {}); template<class T, class Abi, ranges::contiguous_range R, class... Flags> requires ranges::sized_range<R>&& indirectly_writable<ranges::iterator_t<R>, T>constexpr void unchecked_store(const basic_vec<T, Abi>& v, R&& r, const typename basic_vec<T, Abi>::mask_type& mask, flags<Flags...> f = {}); template<class T, class Abi, contiguous_iterator I, class... Flags>requires indirectly_writable<I, T>constexpr void unchecked_store(const basic_vec<T, Abi>& v, I first, iter_difference_t<I> n, flags<Flags...> f = {}); template<class T, class Abi, contiguous_iterator I, class... Flags>requires indirectly_writable<I, T>constexpr void unchecked_store(const basic_vec<T, Abi>& v, I first, iter_difference_t<I> n, const typename basic_vec<T, Abi>::mask_type& mask, flags<Flags...> f = {}); template<class T, class Abi, contiguous_iterator I, sized_sentinel_for<I> S, class... Flags>requires indirectly_writable<I, T>constexpr void unchecked_store(const basic_vec<T, Abi>& v, I first, S last, flags<Flags...> f = {}); template<class T, class Abi, contiguous_iterator I, sized_sentinel_for<I> S, class... Flags>requires indirectly_writable<I, T>constexpr void unchecked_store(const basic_vec<T, Abi>& v, I first, S last, const typename basic_vec<T, Abi>::mask_type& mask, flags<Flags...> f = {}); template<class T, class Abi, ranges::contiguous_range R, class... Flags> requires ranges::sized_range<R>&& indirectly_writable<ranges::iterator_t<R>, T>constexpr void partial_store(const basic_vec<T, Abi>& v, R&& r, flags<Flags...> f = {}); template<class T, class Abi, ranges::contiguous_range R, class... Flags> requires ranges::sized_range<R>&& indirectly_writable<ranges::iterator_t<R>, T>constexpr void partial_store(const basic_vec<T, Abi>& v, R&& r, const typename basic_vec<T, Abi>::mask_type& mask, flags<Flags...> f = {}); template<class T, class Abi, contiguous_iterator I, class... Flags>requires indirectly_writable<I, T>constexpr void partial_store( const basic_vec<T, Abi>& v, I first, iter_difference_t<I> n, flags<Flags...> f = {}); template<class T, class Abi, contiguous_iterator I, class... Flags>requires indirectly_writable<I, T>constexpr void partial_store( const basic_vec<T, Abi>& v, I first, iter_difference_t<I> n, const typename basic_vec<T, Abi>::mask_type& mask, flags<Flags...> f = {}); template<class T, class Abi, contiguous_iterator I, sized_sentinel_for<I> S, class... Flags>requires indirectly_writable<I, T>constexpr void partial_store(const basic_vec<T, Abi>& v, I first, S last, flags<Flags...> f = {}); template<class T, class Abi, contiguous_iterator I, sized_sentinel_for<I> S, class... Flags>requires indirectly_writable<I, T>constexpr void partial_store(const basic_vec<T, Abi>& v, I first, S last, const typename basic_vec<T, Abi>::mask_type& mask, flags<Flags...> f = {}); […]
Modify 29.10.7.2 [simd.ctor] as indicated:
template<class U> constexpr explicit(see below) basic_vec(U&& value) noexcept;-1- Let
-2- Constraints:Fromdenote the typeremove_cvref_t<U>.satisfiesvalue_typeU. […]constructible_from<U>explicitly-convertible-to<value_type>template<class U, class UAbi> constexpr explicit(see below) basic_vec(const basic_vec<U, UAbi>& x) noexcept;-5- Constraints:
[…]
(5.1) —
simd-size-v<U, UAbi> == size()istrue, and(5.2) —
Usatisfiesexplicitly-convertible-to<T>.template<class R, class... Flags> constexpr basic_vec(R&& r, flags<Flags...> = {}); template<class R, class... Flags> constexpr basic_vec(R&& r, const mask_type& mask, flags<Flags...> = {});-12- Let
-13- Constraints:maskbemask_type(true)for the overload with nomaskparameter.
(13.1) —
Rmodelsranges::contiguous_rangeandranges::sized_range,(13.2) —
ranges::size(r)is a constant expression,and(13.3) —
ranges::size(r)is equal tosize(), and(13.?) —
ranges::range_value_t<R>is a vectorizable type and satisfiesexplicitly-convertible-to<T>.-14- Mandates:
(14.1) —ranges::range_value_t<R>is a vectorizable type, and
(14.2) — ifIf the template parameter packFlagsdoes not containconvert-flag, then the conversion fromranges::range_value_t<R>tovalue_typeis value-preserving.
Modify 29.10.8.7 [simd.loadstore] as indicated:
template<class V = see below , ranges::contiguous_range R, class... Flags> requires ranges::sized_range<R> constexpr V partial_load(R&& r, flags<Flags...> f = {}); template<class V = see below , ranges::contiguous_range R, class... Flags> requires ranges::sized_range<R> constexpr V partial_load(R&& r, const typename V::mask_type& mask, flags<Flags...> f = {}); template<class V = see below , contiguous_iterator I, class... Flags> constexpr V partial_load(I first, iter_difference_t<I> n, flags<Flags...> f = {}); template<class V = see below , contiguous_iterator I, class... Flags> constexpr V partial_load(I first, iter_difference_t<I> n, const typename V::mask_type& mask, flags<Flags...> f = {}); template<class V = see below , contiguous_iterator I, sized_sentinel_for<I> S, class... Flags> constexpr V partial_load(I first, S last, flags<Flags...> f = {}); template<class V = see below , contiguous_iterator I, sized_sentinel_for<I> S, class... Flags> constexpr V partial_load(I first, S last, const typename V::mask_type& mask, flags<Flags...> f = {});-6- Let […]
[…] -7- Mandates:
(7.1) —
ranges::range_value_t<R>is a vectorizable type and satisfiesexplicitly-convertible-to<T>,(7.2) —
same_as<remove_cvref_t<V>, V>istrue,(7.3) —
Vis an enabled specialization ofbasic_vec, and(7.4) — if the template parameter pack
Flagsdoes not containconvert-flag, then the conversion fromranges::range_value_t<R>toV::value_typeis value-preserving.template<class T, class Abi, ranges::contiguous_range R, class... Flags> requires ranges::sized_range<R>&& indirectly_writable<ranges::iterator_t<R>, T>constexpr void unchecked_store(const basic_vec<T, Abi>& v, R&& r, flags<Flags...> f = {}); template<class T, class Abi, ranges::contiguous_range R, class... Flags> requires ranges::sized_range<R>&& indirectly_writable<ranges::iterator_t<R>, T>constexpr void unchecked_store(const basic_vec<T, Abi>& v, R&& r, const typename basic_vec<T, Abi>::mask_type& mask, flags<Flags...> f = {}); template<class T, class Abi, contiguous_iterator I, class... Flags>requires indirectly_writable<I, T>constexpr void unchecked_store(const basic_vec<T, Abi>& v, I first, iter_difference_t<I> n, flags<Flags...> f = {}); template<class T, class Abi, contiguous_iterator I, class... Flags>requires indirectly_writable<I, T>constexpr void unchecked_store(const basic_vec<T, Abi>& v, I first, iter_difference_t<I> n, const typename basic_vec<T, Abi>::mask_type& mask, flags<Flags...> f = {}); template<class T, class Abi, contiguous_iterator I, sized_sentinel_for<I> S, class... Flags>requires indirectly_writable<I, T>constexpr void unchecked_store(const basic_vec<T, Abi>& v, I first, S last, flags<Flags...> f = {}); template<class T, class Abi, contiguous_iterator I, sized_sentinel_for<I> S, class... Flags>requires indirectly_writable<I, T>constexpr void unchecked_store(const basic_vec<T, Abi>& v, I first, S last, const typename basic_vec<T, Abi>::mask_type& mask, flags<Flags...> f = {});-11- Let […]
[…]template<class T, class Abi, ranges::contiguous_range R, class... Flags> requires ranges::sized_range<R>&& indirectly_writable<ranges::iterator_t<R>, T>constexpr void partial_store(const basic_vec<T, Abi>& v, R&& r, flags<Flags...> f = {}); template<class T, class Abi, ranges::contiguous_range R, class... Flags> requires ranges::sized_range<R>&& indirectly_writable<ranges::iterator_t<R>, T>constexpr void partial_store(const basic_vec<T, Abi>& v, R&& r, const typename basic_vec<T, Abi>::mask_type& mask, flags<Flags...> f = {}); template<class T, class Abi, contiguous_iterator I, class... Flags>requires indirectly_writable<I, T>constexpr void partial_store( const basic_vec<T, Abi>& v, I first, iter_difference_t<I> n, flags<Flags...> f = {}); template<class T, class Abi, contiguous_iterator I, class... Flags>requires indirectly_writable<I, T>constexpr void partial_store( const basic_vec<T, Abi>& v, I first, iter_difference_t<I> n, const typename basic_vec<T, Abi>::mask_type& mask, flags<Flags...> f = {}); template<class T, class Abi, contiguous_iterator I, sized_sentinel_for<I> S, class... Flags>requires indirectly_writable<I, T>constexpr void partial_store(const basic_vec<T, Abi>& v, I first, S last, flags<Flags...> f = {}); template<class T, class Abi, contiguous_iterator I, sized_sentinel_for<I> S, class... Flags>requires indirectly_writable<I, T>constexpr void partial_store(const basic_vec<T, Abi>& v, I first, S last, const typename basic_vec<T, Abi>::mask_type& mask, flags<Flags...> f = {});-15- Let […]
-?- Constraints:
(?.1) —
ranges::iterator_t<R>satisfiesindirectly_writable<ranges::range_value_t<R>>, and(?.2) —
Tsatisfiesexplicitly-convertible-to<ranges::range_value_t<R>>-16- Mandates: […]
-17- Preconditions: […] -18- Effects: For alliin the range of[0, basic_vec<T, Abi>::size()), ifmask[i] && i < ranges::size(r)istrue, evaluatesranges::data(r)[i] = static_cast<ranges::range_value_t<R>>(v[i]).
Modify 29.10.8.11 [simd.permute.memory] as indicated:
template<class V = see below, ranges::contiguous_range R, simd-integral I, class... Flags> requires ranges::sized_range<R> constexpr V partial_gather_from(R&& in, const I& indices, flags<Flags...> f = {}); template<class V = see below, ranges::contiguous_range R, simd-integral I, class... Flags> requires ranges::sized_range<R> constexpr V partial_gather_from(R&& in, const typename I::mask_type& mask, const I& indices, flags<Flags...> f = {});-5- Let: […]
-?- Constraints:ranges::range_value_t<R>is a vectorizable type and satisfiesexplicitly-convertible-to<T>.-6- Mandates: […]
[…]template<simd-vec-type V, ranges::contiguous_range R, simd-integral I, class... Flags> requires ranges::sized_range<R> constexpr void partial_scatter_to(const V& v, R&& out, const I& indices, flags<Flags...> f = {}); template<simd-vec-type V, ranges::contiguous_range R, simd-integral I, class... Flags> requires ranges::sized_range<R> constexpr void partial_scatter_to(const V& v, R&& out, const typename I::mask_type& mask, const I& indices, flags<Flags...> f = {});-13- Let
-14- Constraints:maskbeI::mask_type(true)for the overload with nomaskparameter.
(14.1) —
V::size() == I::size()istrue,(14.2) —
ranges::iterator_t<R>satisfiesindirectly_writable<ranges::range_value_t<R>>, and(14.3) —
typename V::value_typesatisfiesexplicitly-convertible-to<ranges::range_value_t<R>>.