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.

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

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

Priority: Not Prioritized

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

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

View all issues with New status.

Discussion:

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.

Proposed resolution:

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: […]