This page is a snapshot from the LWG issues list, see the Library Active Issues List for more information and the meaning of WP status.

4376. ABI tag in return type of [simd.mask.unary] is overconstrained

Section: 29.10.9.4 [simd.mask.unary] Status: WP Submitter: Matthias Kretz Opened: 2025-09-15 Last modified: 2025-11-11

Priority: 1

View all other issues in [simd.mask.unary].

View all issues with WP status.

Discussion:

Addresses DE 298

29.10.9.4 [simd.mask.unary] spells out the return type with the ABI tag of the basic_mask specialization. That's problematic / overconstrained.

  1. Consider Intel SandyBridge/IvyBridge-like targets:

    vec<float>::size() -> 8
    vec<int>::size() -> 4
    mask<float>::size() -> 8
    

    The ABI tag in this case encodes for vec<float> that one object holds 8 elements and is passed via one register. vec<int> uses a different ABI tag that says 4 elements passed via one register. vec<int, 8>'s ABI tag says 8 elements passed via two registers.

    Now what should +mask<float>() return? The working draft says it must return a basic_vec<int, mask<float>::abi_type>. And mask<float>::abi_type is constrained to be the same as vec<float>::abi_type. The working draft thus makes it impossible to implement ABI tags that encode number of elements + number of registers (+ bit-masks vs. vector-masks, but that's irrelevant for this issue). Instead, an ABI tag would have to encode the native SIMD width of all vectorizable types. And that's unnecessarily making compatible types incompatible. Also we make it harder to add to the set of vectorizable types in the future.

  2. The issue is even worse for an implementation that implements vec<complex<T>> using different ABI tags. Encoding whether the value-type is complex into the ABI is useful because it impacts how the mask is stored (mask<complex<float>, 8> is internally stored as a 16-element bit-mask (for interleaved complex), while mask<double, 8> is stored as an 8-element bit-mask). The ABI tag can also be used to implement interleaved vs. contiguous storage, which is useful for different architectures. If we require +mask<complex<float>>() to be of a different type than any vec<long long> would ever be, that's just brittle and unnecessary template bloat.

[2025-10-17; Reflector poll.]

Set priority to 1 after reflector poll.

"Should be addressed together with 4238(i)."

Previous resolution [SUPERSEDED]:

This wording is relative to N5014.

[Drafting note: LWG 4238(i) is closely related.]

  1. Modify 29.10.2 [simd.expos] as indicated:

    using simd-size-type = see below;                      // exposition only
    template<size_t Bytes> using integer-from = see below; // exposition only
    
    template<class T, class Abi>
      constexpr simd-size-type simd-size-v = see below;               // exposition only
    template<class T> constexpr size_t mask-element-size = see below; // exposition only
    
    template <size_t Bytes, class Abi>
      using simd-vec-from-mask-t = see below;                         // exposition only
    […]
    
  2. Modify 29.10.2.1 [simd.expos.defn] as indicated:

    template<class T> constexpr size_t mask-element-size = see below; // exposition only
    

    -4- mask-element-size<basic_mask<Bytes, Abi>> has the value Bytes.

    template <size_t Bytes, class Abi>
      using simd-vec-from-mask-t = see below;
    

    -?- simd-vec-from-mask-t<Bytes, Abi> is an alias for an enabled specialization of basic_vec if and only if basic_mask<Bytes, Abi> is a data-parallel type and integer-from<Bytes> is valid and a vectorizable type.

    -?- simd-vec-from-mask-t<Bytes, Abi>::size() == basic_mask<Bytes, Abi>::size() is true.

    -?- typename simd-vec-from-mask-t<Bytes, Abi>::value_type is integer-from<Bytes>

  3. Modify 29.10.9.1 [simd.mask.overview], class template basic_mask overview synopsis, as indicated:

    namespace std::simd {
      template<size_t Bytes, class Abi> class basic_mask {
      public:
        […]
        // 29.10.9.4 [simd.mask.unary], basic_mask unary operators
        constexpr basic_mask operator!() const noexcept;
        constexpr basic_vec<integer-from<Bytes>, Abi>simd-vec-from-mask-t<Bytes, Abi> operator+() const noexcept;
        constexpr basic_vec<integer-from<Bytes>, Abi>simd-vec-from-mask-t<Bytes, Abi> operator-() const noexcept;
        constexpr basic_vec<integer-from<Bytes>, Abi>simd-vec-from-mask-t<Bytes, Abi> operator~() const noexcept;    
        […]
    }
    
  4. Modify 29.10.9.4 [simd.mask.unary] as indicated:

    constexpr basic_mask operator!() const noexcept;
    constexpr basic_vec<integer-from<Bytes>, Abi>simd-vec-from-mask-t<Bytes, Abi> operator+() const noexcept;
    constexpr basic_vec<integer-from<Bytes>, Abi>simd-vec-from-mask-t<Bytes, Abi> operator-() const noexcept;
    constexpr basic_vec<integer-from<Bytes>, Abi>simd-vec-from-mask-t<Bytes, Abi> operator~() const noexcept;    
    

    -1- Let op be the operator.

    -2- Returns: […]

[2025-11-04; Matthias Kretz provides new wording]

This also resolves 4238(i) and addresses DE 297.

[Kona 2025-11-04; approved by LWG. Status changed: New → Immediate.]

[Kona 2025-11-08; Status changed: Immediate → WP.]

Proposed resolution:

This wording is relative to N5014.

  1. Modify 29.10.9.1 [simd.mask.overview], class template basic_mask overview synopsis, as indicated:

    namespace std::simd {
      template<size_t Bytes, class Abi> class basic_mask {
      public:
        […]
        // 29.10.9.4 [simd.mask.unary], basic_mask unary operators
        constexpr basic_mask operator!() const noexcept;
        constexpr basic_vec<integer-from<Bytes>, Abi>see below operator+() const noexcept;
        constexpr basic_vec<integer-from<Bytes>, Abi>see below operator-() const noexcept;
        constexpr basic_vec<integer-from<Bytes>, Abi>see below operator~() const noexcept;
        […]
    }
    
  2. Modify 29.10.9.4 [simd.mask.unary] as indicated:

    constexpr basic_mask operator!() const noexcept;
    constexpr basic_vec<integer-from<Bytes>, Abi>see below operator+() const noexcept;
    constexpr basic_vec<integer-from<Bytes>, Abi>see below operator-() const noexcept;
    constexpr basic_vec<integer-from<Bytes>, Abi>see below operator~() const noexcept;
    

    -1- Let op be the operator.

    -2- Returns: A data-parallel object where the i-th element is initialized to the results of applying op to operator[](i) for all i in the range of [0, size()).

    -?- Remarks: If there exists a vectorizable signed integer type I such that sizeof(I) == Bytes is true, operator+, operator-, and operator~ return an enabled specialization R of basic_vec such that R::value_type denotes integer-from<Bytes> and R::size() == size() is true. Otherwise, these operators are defined as deleted and their return types are unspecified.