simd_mask.h
// Implementation of <simd> -*- C++ -*-
// Copyright The GNU Toolchain Authors.
//
// This file is part of the GNU ISO C++ Library. This library is free
// software; you can redistribute it and/or modify it under the
// terms of the GNU General Public License as published by the
// Free Software Foundation; either version 3, or (at your option)
// any later version.
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// Under Section 7 of GPL version 3, you are granted additional
// permissions described in the GCC Runtime Library Exception, version
// 3.1, as published by the Free Software Foundation.
// You should have received a copy of the GNU General Public License and
// a copy of the GCC Runtime Library Exception along with this program;
// see the files COPYING3 and COPYING.RUNTIME respectively. If not, see
// <http://www.gnu.org/licenses/>.
#ifndef _GLIBCXX_SIMD_MASK_H
#define _GLIBCXX_SIMD_MASK_H 1
#ifdef _GLIBCXX_SYSHDR
#pragma GCC system_header
#endif
#if __cplusplus >= 202400L
#include "simd_iterator.h"
#include "vec_ops.h"
#if _GLIBCXX_X86
#include "simd_x86.h"
#endif
#include <bit>
#include <bitset>
// psabi warnings are bogus because the ABI of the internal types never leaks into user code
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wpsabi"
namespace std _GLIBCXX_VISIBILITY(default)
{
_GLIBCXX_BEGIN_NAMESPACE_VERSION
namespace simd
{
template <unsigned _Np>
struct _SwapNeighbors
{
consteval unsigned
operator()(unsigned __i, unsigned __size) const
{
if (__size % (2 * _Np) != 0)
__builtin_abort(); // swap_neighbors<N> permutation requires a multiple of 2N elements
else if (std::has_single_bit(_Np))
return __i ^ _Np;
else if (__i % (2 * _Np) >= _Np)
return __i - _Np;
else
return __i + _Np;
}
};
template <size_t _Np, size_t _Mp>
constexpr auto
__bitset_split(const bitset<_Mp>& __b)
{
constexpr auto __bits_per_word = __CHAR_BIT__ * __SIZEOF_LONG__;
if constexpr (_Np % __bits_per_word == 0)
{
struct _Tmp
{
bitset<_Np> _M_lo;
bitset<_Mp - _Np> _M_hi;
};
return __builtin_bit_cast(_Tmp, __b);
}
else
{
constexpr auto __bits_per_ullong = __CHAR_BIT__ * __SIZEOF_LONG_LONG__;
static_assert(_Mp <= __bits_per_ullong);
using _Lo = _Bitmask<_Np>;
using _Hi = _Bitmask<_Mp - _Np>;
struct _Tmp
{
_Lo _M_lo;
_Hi _M_hi;
};
return _Tmp {static_cast<_Lo>(__b.to_ullong()), static_cast<_Hi>(__b.to_ullong() >> _Np)};
}
}
static_assert(__bitset_split<64>(bitset<128>(1))._M_lo == bitset<64>(1));
static_assert(__bitset_split<64>(bitset<128>(1))._M_hi == bitset<64>(0));
// [simd.traits]
// --- rebind ---
template <typename _Tp, typename _Vp, _ArchTraits _Traits = {}>
struct rebind
{};
/**
* Computes a member @c type `basic_vec<_Tp, Abi>`, where @c Abi is chosen such that the
* number of elements is equal to `_Vp::size()` and features of the ABI tag (such as the
* internal representation of masks, or storage order of complex components) are preserved.
*/
template <__vectorizable _Tp, __simd_vec_type _Vp, _ArchTraits _Traits>
//requires requires { typename __deduce_abi_t<_Tp, _Vp::size()>; }
struct rebind<_Tp, _Vp, _Traits>
{ using type = __similar_vec<_Tp, _Vp::size(), typename _Vp::abi_type>; };
/**
* As above, except for @c basic_mask.
*/
template <__vectorizable _Tp, __simd_mask_type _Mp, _ArchTraits _Traits>
//requires requires { typename __deduce_abi_t<_Tp, _Mp::size()>; }
struct rebind<_Tp, _Mp, _Traits>
{ using type = __similar_mask<_Tp, _Mp::size(), typename _Mp::abi_type>; };
template <typename _Tp, typename _Vp>
using rebind_t = typename rebind<_Tp, _Vp>::type;
// --- resize ---
template <__simd_size_type _Np, typename _Vp, _ArchTraits _Traits = {}>
struct resize
{};
template <__simd_size_type _Np, __simd_vec_type _Vp, _ArchTraits _Traits>
requires (_Np >= 1)
//requires requires { typename __deduce_abi_t<typename _Vp::value_type, _Np>; }
struct resize<_Np, _Vp, _Traits>
{ using type = __similar_vec<typename _Vp::value_type, _Np, typename _Vp::abi_type>; };
template <__simd_size_type _Np, __simd_mask_type _Mp, _ArchTraits _Traits>
requires (_Np >= 1)
//requires requires { typename __deduce_abi_t<typename _Mp::value_type, _Np>; }
struct resize<_Np, _Mp, _Traits>
{
using _A1 = decltype(__abi_rebind<__mask_element_size<_Mp>, _Np, typename _Mp::abi_type,
true>());
static_assert(__abi_tag<_A1>);
static_assert(_Mp::abi_type::_S_variant == _A1::_S_variant || __scalar_abi_tag<_A1>
|| __scalar_abi_tag<typename _Mp::abi_type>);
using type = basic_mask<__mask_element_size<_Mp>, _A1>;
};
template <__simd_size_type _Np, typename _Vp>
using resize_t = typename resize<_Np, _Vp>::type;
// [simd.syn]
inline constexpr __simd_size_type zero_element = numeric_limits<int>::min();
inline constexpr __simd_size_type uninit_element = zero_element + 1;
// [simd.permute.static]
template<__simd_size_type _Np = 0, __simd_vec_or_mask_type _Vp,
__index_permutation_function<_Vp> _IdxMap>
[[__gnu__::__always_inline__]]
constexpr resize_t<_Np == 0 ? _Vp::size() : _Np, _Vp>
permute(const _Vp& __v, _IdxMap&& __idxmap)
{ return resize_t<_Np == 0 ? _Vp::size() : _Np, _Vp>::_S_static_permute(__v, __idxmap); }
// [simd.permute.dynamic]
template<__simd_vec_or_mask_type _Vp, __simd_integral _Ip>
[[__gnu__::__always_inline__]]
constexpr resize_t<_Ip::size(), _Vp>
permute(const _Vp& __v, const _Ip& __indices)
{ return __v[__indices]; }
// [simd.creation] ----------------------------------------------------------
template<__simd_vec_type _Vp, typename _Ap>
[[__gnu__::__always_inline__]]
constexpr auto
chunk(const basic_vec<typename _Vp::value_type, _Ap>& __x) noexcept
{ return __x.template _M_chunk<_Vp>(); }
template<__simd_mask_type _Mp, typename _Ap>
[[__gnu__::__always_inline__]]
constexpr auto
chunk(const basic_mask<__mask_element_size<_Mp>, _Ap>& __x) noexcept
{ return __x.template _M_chunk<_Mp>(); }
template<__simd_size_type _Np, typename _Tp, typename _Ap>
[[__gnu__::__always_inline__]]
constexpr auto
chunk(const basic_vec<_Tp, _Ap>& __x) noexcept
-> decltype(chunk<resize_t<_Np, basic_vec<_Tp, _Ap>>>(__x))
{ return chunk<resize_t<_Np, basic_vec<_Tp, _Ap>>>(__x); }
template<__simd_size_type _Np, size_t _Bytes, typename _Ap>
[[__gnu__::__always_inline__]]
constexpr auto
chunk(const basic_mask<_Bytes, _Ap>& __x) noexcept
-> decltype(chunk<resize_t<_Np, basic_mask<_Bytes, _Ap>>>(__x))
{ return chunk<resize_t<_Np, basic_mask<_Bytes, _Ap>>>(__x); }
// LWG???? (reported 2025-11-25)
template<typename _Tp, typename _A0, typename... _Abis>
constexpr resize_t<(_A0::_S_size + ... + _Abis::_S_size), basic_vec<_Tp, _A0>>
cat(const basic_vec<_Tp, _A0>& __x0, const basic_vec<_Tp, _Abis>&... __xs) noexcept
{
return resize_t<(_A0::_S_size + ... + _Abis::_S_size), basic_vec<_Tp, _A0>>
::_S_concat(__x0, __xs...);
}
// LWG???? (reported 2025-11-25)
template<size_t _Bytes, typename _A0, typename... _Abis>
constexpr resize_t<(_A0::_S_size + ... + _Abis::_S_size), basic_mask<_Bytes, _A0>>
cat(const basic_mask<_Bytes, _A0>& __x0, const basic_mask<_Bytes, _Abis>&... __xs) noexcept
{
return resize_t<(_A0::_S_size + ... + _Abis::_S_size), basic_mask<_Bytes, _A0>>
::_S_concat(__x0, __xs...);
}
// implementation helper for chunk and cat
consteval int
__packs_to_skip_at_front(int __offset, initializer_list<int> __sizes)
{
int __i = 0;
int __n = 0;
for (int __s : __sizes)
{
__n += __s;
if (__n > __offset)
return __i;
++__i;
}
__builtin_trap(); // called out of contract
}
consteval int
__packs_to_skip_at_back(int __offset, int __max, initializer_list<int> __sizes)
{
int __i = 0;
int __n = -__offset;
for (int __s : __sizes)
{
++__i;
__n += __s;
if (__n >= __max)
return int(__sizes.size()) - __i;
}
return 0;
}
// in principle, this overload allows conversions to _Dst - and it wouldn't be wrong - but the
// general overload below is still a better candidate in overload resolution
template <typename _Dst>
[[__gnu__::__always_inline__]]
constexpr _Dst
__extract_simd_at(auto _Offset, const _Dst& __r, const auto&...)
requires(_Offset.value == 0)
{ return __r; }
template <typename _Dst, typename _V0>
[[__gnu__::__always_inline__]]
constexpr _Dst
__extract_simd_at(auto _Offset, const _V0&, const _Dst& __r, const auto&...)
requires(_Offset.value == _V0::size.value)
{ return __r; }
template <typename _Dst, typename... _Vs>
[[__gnu__::__always_inline__]]
constexpr _Dst
__extract_simd_at(auto _Offset, const _Vs&... __xs)
{
using _Adst = typename _Dst::abi_type;
if constexpr (_Adst::_S_nreg >= 2)
{
using _Dst0 = remove_cvref_t<decltype(declval<_Dst>()._M_get_low())>;
using _Dst1 = remove_cvref_t<decltype(declval<_Dst>()._M_get_high())>;
return _Dst::_S_init(__extract_simd_at<_Dst0>(_Offset, __xs...),
__extract_simd_at<_Dst1>(_Offset + _Dst0::size, __xs...));
}
else
{
using _Ret = remove_cvref_t<decltype(declval<_Dst>()._M_get())>;
constexpr bool __use_bitmask = __simd_mask_type<_Dst> && _Adst::_S_is_bitmask;
constexpr int __dst_full_size = __bit_ceil(unsigned(_Adst::_S_size));
constexpr int __nargs = sizeof...(__xs);
using _Afirst = typename _Vs...[0]::abi_type;
using _Alast = typename _Vs...[__nargs - 1]::abi_type;
const auto& __x0 = __xs...[0];
const auto& __xlast = __xs...[__nargs - 1];
constexpr int __ninputs = (_Vs::size.value + ...);
if constexpr (_Offset.value >= _Afirst::_S_size
|| __ninputs - _Offset.value - _Alast::_S_size >= _Adst::_S_size)
{ // can drop inputs at the front and/or back of the pack
constexpr int __skip_front = __packs_to_skip_at_front(_Offset.value,
{_Vs::size.value...});
constexpr int __skip_back = __packs_to_skip_at_back(_Offset.value, _Adst::_S_size,
{_Vs::size.value...});
static_assert(__skip_front > 0 || __skip_back > 0);
constexpr auto [...__skip] = _IotaArray<__skip_front>;
constexpr auto [...__is] = _IotaArray<__nargs - __skip_front - __skip_back>;
constexpr int __new_offset = _Offset.value - (0 + ... + _Vs...[__skip]::size.value);
return __extract_simd_at<_Dst>(cw<__new_offset>, __xs...[__is + __skip_front]...);
}
else if constexpr (_Adst::_S_size == 1)
{ // trivial conversion to one value_type
return _Dst(__x0[_Offset.value]);
}
else if constexpr (_Afirst::_S_nreg >= 2 || _Alast::_S_nreg >= 2)
{ // flatten first and/or last multi-register argument
constexpr bool __flatten_first = _Afirst::_S_nreg >= 2;
constexpr bool __flatten_last = __nargs > 1 && _Alast::_S_nreg >= 2;
constexpr auto [...__is] = _IotaArray<__nargs - __flatten_first - __flatten_last>;
if constexpr (__flatten_first && __flatten_last)
return __extract_simd_at<_Dst>(
_Offset, __x0._M_get_low(), __x0._M_get_high(), __xs...[__is + 1]...,
__xlast._M_get_low(), __xlast._M_get_high());
else if constexpr (__flatten_first)
return __extract_simd_at<_Dst>(
_Offset, __x0._M_get_low(), __x0._M_get_high(), __xs...[__is + 1]...);
else
return __extract_simd_at<_Dst>(
_Offset, __xs...[__is]..., __xlast._M_get_low(), __xlast._M_get_high());
}
else if constexpr (__simd_mask_type<_Dst>
&& ((_Adst::_S_variant != _Vs::abi_type::_S_variant
&& !__scalar_abi_tag<typename _Vs::abi_type>) || ...))
{ // convert ABI tag if incompatible
return __extract_simd_at<_Dst>(
_Offset, static_cast<const resize_t<_Vs::size.value, _Dst>&>(__xs)...);
}
// at this point __xs should be as small as possible; there may be some corner cases left
else if constexpr (__nargs == 1)
{ // simple and optimal
if constexpr (__use_bitmask)
return _Dst(_Ret(__x0._M_to_uint() >> _Offset.value));
else
return _VecOps<_Ret>::_S_extract(__x0._M_concat_data(false), _Offset);
}
else if constexpr (__use_bitmask)
{ // fairly simple and optimal bit shifting solution
static_assert(_Afirst::_S_nreg == 1);
static_assert(_Offset.value < _Afirst::_S_size);
int __offset = -_Offset.value;
_Ret __r;
template for (const auto& __x : {__xs...})
{
if (__offset <= 0)
__r = _Ret(__x._M_to_uint() >> -__offset);
else if (__offset < _Adst::_S_size)
__r |= _Ret(_Ret(__x._M_to_uint()) << __offset);
__offset += __x.size.value;
}
return _Dst(__r);
}
else if constexpr (__nargs == 2 && _Offset == 0 && _Adst::_S_nreg == 1
&& _Afirst::_S_size >= _Alast::_S_size
&& __has_single_bit(unsigned(_Afirst::_S_size)))
{ // simple __vec_concat
if constexpr (_Afirst::_S_size == 1)
// even simpler init from two values
return _Ret{__x0._M_concat_data()[0], __xlast._M_concat_data()[0]};
else
{
const auto __v0 = __x0._M_concat_data();
const auto __v1 = __vec_zero_pad_to<sizeof(__v0)>(__xlast._M_concat_data());
return __vec_concat(__v0, __v1);
}
}
else if constexpr (__nargs == 2 && _Adst::_S_nreg == 1 && _Offset == 0
&& _Afirst::_S_nreg == 1 && _Alast::_S_size == 1)
{ // optimize insertion of one element at the end
_Ret __r = __vec_zero_pad_to<sizeof(_Ret)>(__x0._M_get());
__vec_set(__r, _Afirst::_S_size, __xlast._M_concat_data()[0]);
return __r;
}
else if constexpr (__nargs == 2 && _Adst::_S_nreg == 1 && _Offset == 0
&& _Afirst::_S_nreg == 1 && _Alast::_S_size == 2)
{ // optimize insertion of two elements at the end
_Ret __r = __vec_zero_pad_to<sizeof(_Ret)>(__x0._M_concat_data());
const auto __x1 = __xlast._M_concat_data();
if constexpr (sizeof(__x1) <= sizeof(double) && (_Afirst::_S_size & 1) == 0)
{ // can use a single insert instruction
using _Up = __conditional_t<
is_floating_point_v<__vec_value_type<_Ret>>,
__conditional_t<sizeof(__x1) == sizeof(double), double, float>,
__integer_from<sizeof(__x1)>>;
auto __r2 = __vec_bit_cast<_Up>(__r);
__vec_set(__r2, _Afirst::_S_size / 2, __vec_bit_cast<_Up>(__x1)[0]);
__r = reinterpret_cast<_Ret>(__r2);
}
else
{
__vec_set(__r, _Afirst::_S_size, __x1[0]);
__vec_set(__r, _Afirst::_S_size + 1, __x1[1]);
}
return __r;
}
else if constexpr (__nargs == 2 && _Afirst::_S_nreg == 1 && _Alast::_S_nreg == 1)
{ // optimize concat of two input vectors (e.g. using palignr)
constexpr auto [...__is] = _IotaArray<__dst_full_size>;
constexpr int __v2_offset = __width_of<decltype(__x0._M_concat_data())>;
return __builtin_shufflevector(
__x0._M_concat_data(), __xlast._M_concat_data(), [](int __i) consteval {
if (__i < _Afirst::_S_size)
return __i;
__i -= _Afirst::_S_size;
if (__i < _Alast::_S_size)
return __i + __v2_offset;
else
return -1;
}(__is + _Offset.value)...);
}
else if (__is_const_known(__xs...) || __ninputs == _Adst::_S_size)
{ // hard to optimize for the compiler, but necessary in constant expressions
return _VecOps<_Ret>::_S_extract(
__vec_concat_sized<__xs.size.value...>(__xs._M_concat_data(false)...),
_Offset);
}
else
{ // fallback to concatenation in memory => load the result
alignas(_Ret) __vec_value_type<_Ret>
__tmp[std::max(__ninputs, _Offset.value + __dst_full_size)] = {};
int __offset = 0;
template for (const auto& __x : {__xs...})
{
if constexpr (__simd_mask_type<_Dst>)
(-__x)._M_store(__tmp + __offset);
else
__x._M_store(__tmp + __offset);
__offset += __x.size.value;
}
_Ret __r;
__builtin_memcpy(&__r, __tmp + _Offset.value, sizeof(_Ret));
return __r;
}
}
}
// [simd.mask] --------------------------------------------------------------
template <size_t _Bytes, typename _Ap>
class basic_mask
{
public:
using value_type = bool;
using abi_type = _Ap;
#define _GLIBCXX_DELETE_SIMD "This specialization is disabled because of an invalid combination " \
"of template arguments to basic_mask."
basic_mask() = delete(_GLIBCXX_DELETE_SIMD);
~basic_mask() = delete(_GLIBCXX_DELETE_SIMD);
basic_mask(const basic_mask&) = delete(_GLIBCXX_DELETE_SIMD);
basic_mask& operator=(const basic_mask&) = delete(_GLIBCXX_DELETE_SIMD);
#undef _GLIBCXX_DELETE_SIMD
};
template <size_t _Bytes, typename _Ap>
class _MaskBase
{
using _Mp = basic_mask<_Bytes, _Ap>;
protected:
using _VecType = __simd_vec_from_mask_t<_Bytes, _Ap>;
static_assert(destructible<_VecType> || _Bytes > sizeof(0ull));
public:
using iterator = __iterator<_Mp>;
using const_iterator = __iterator<const _Mp>;
constexpr iterator
begin() noexcept
{ return {static_cast<_Mp&>(*this), 0}; }
constexpr const_iterator
begin() const noexcept
{ return cbegin(); }
constexpr const_iterator
cbegin() const noexcept
{ return {static_cast<const _Mp&>(*this), 0}; }
constexpr default_sentinel_t
end() const noexcept
{ return {}; }
constexpr default_sentinel_t
cend() const noexcept
{ return {}; }
static constexpr auto size = __simd_size_c<_Ap::_S_size>;
_MaskBase() = default;
// LWG issue from 2026-03-04 / P4042R0
template <size_t _UBytes, typename _UAbi>
requires (_Ap::_S_size != _UAbi::_S_size)
explicit
_MaskBase(const basic_mask<_UBytes, _UAbi>&) = delete("size mismatch");
template <typename _Up, typename _UAbi>
explicit
_MaskBase(const basic_vec<_Up, _UAbi>&)
= delete("use operator! or a comparison to convert a vec into a mask");
template <typename _Up, typename _UAbi>
requires (_Ap::_S_size != _UAbi::_S_size)
operator basic_vec<_Up, _UAbi>() const
= delete("size mismatch");
};
template <size_t _Bytes, __abi_tag _Ap>
requires (_Ap::_S_nreg == 1)
class basic_mask<_Bytes, _Ap>
: public _MaskBase<_Bytes, _Ap>
{
using _Base = _MaskBase<_Bytes, _Ap>;
using _VecType = _Base::_VecType;
template <size_t, typename>
friend class basic_mask;
template <typename, typename>
friend class basic_vec;
static constexpr int _S_size = _Ap::_S_size;
using _DataType = typename _Ap::template _MaskDataType<_Bytes>;
static constexpr bool _S_has_bool_member = is_same_v<_DataType, bool>;
static constexpr bool _S_is_scalar = _S_has_bool_member;
static constexpr bool _S_use_bitmask = _Ap::_S_is_bitmask;
static constexpr int _S_full_size = [] {
if constexpr (_S_is_scalar)
return _S_size;
else if constexpr (_S_use_bitmask && _S_size < __CHAR_BIT__)
return __CHAR_BIT__;
else
return __bit_ceil(unsigned(_S_size));
}();
static constexpr bool _S_is_partial = _S_size != _S_full_size;
static constexpr _DataType _S_implicit_mask = [] {
if constexpr (_S_is_scalar)
return true;
else if (!_S_is_partial)
return _DataType(~_DataType());
else if constexpr (_S_use_bitmask)
return _DataType((_DataType(1) << _S_size) - 1);
else
{
constexpr auto [...__is] = _IotaArray<_S_full_size>;
return _DataType{ (__is < _S_size ? -1 : 0)... };
}
}();
// Actual padding bytes, not padding elements.
// => _S_padding_bytes is 0 even if _S_is_partial is true.
static constexpr size_t _S_padding_bytes = 0;
_DataType _M_data;
public:
using value_type = bool;
using abi_type = _Ap;
using iterator = _Base::iterator;
using const_iterator = _Base::const_iterator;
// internal but public API ----------------------------------------------
[[__gnu__::__always_inline__]]
static constexpr basic_mask
_S_init(_DataType __x)
{
basic_mask __r;
__r._M_data = __x;
return __r;
}
[[__gnu__::__always_inline__]]
static constexpr basic_mask
_S_init(unsigned_integral auto __bits)
{ return basic_mask(__bits); }
[[__gnu__::__always_inline__]]
constexpr const _DataType&
_M_get() const
{ return _M_data; }
/** @internal
* Bit-cast the given object @p __x to basic_mask.
*
* This is necessary for _S_nreg > 1 where the last element can be bool or when the sizeof
* doesn't match because of different alignment requirements of the sub-masks.
*/
template <size_t _UBytes, typename _UAbi>
[[__gnu__::__always_inline__]]
static constexpr basic_mask
_S_recursive_bit_cast(const basic_mask<_UBytes, _UAbi>& __x)
{ return __builtin_bit_cast(basic_mask, __x._M_concat_data()); }
[[__gnu__::__always_inline__]]
constexpr auto
_M_concat_data(bool __do_sanitize = _S_is_partial) const
{
if constexpr (_S_is_scalar)
return __vec_builtin_type<__integer_from<_Bytes>, 1>{__integer_from<_Bytes>(-_M_data)};
else
{
if constexpr (_S_is_partial)
if (__do_sanitize)
return _DataType(_M_data & _S_implicit_mask);
return _M_data;
}
}
/** @internal
* Returns a mask where the first @p __n elements are true. All remaining elements are false.
*
* @pre @p __n > 0 && @p __n < _S_size
*/
template <_ArchTraits _Traits = {}>
[[__gnu__::__always_inline__]]
static constexpr basic_mask
_S_partial_mask_of_n(int __n)
{
static_assert(!_S_is_scalar);
if constexpr (!_S_use_bitmask)
{
using _Ip = __integer_from<_Bytes>;
__glibcxx_simd_precondition(__n >= 0 && __n <= numeric_limits<_Ip>::max(),
"_S_partial_mask_of_n without _S_use_bitmask requires "
"positive __n that does not overflow.");
constexpr _DataType __0123
= __builtin_bit_cast(_DataType, _IotaArray<_Ip(_S_full_size)>);
return basic_mask(__0123 < _Ip(__n));
}
else
{
__glibcxx_simd_precondition(__n >= 0 && __n <= 255,
"The x86 BZHI instruction requires __n to "
"only use bits 0:7");
#if __has_builtin(__builtin_ia32_bzhi_si)
if constexpr (_S_size <= 32 && _Traits._M_have_bmi2())
return _S_init(_Bitmask<_S_size>(
__builtin_ia32_bzhi_si(~0u >> (32 - _S_size), unsigned(__n))));
#endif
#if __has_builtin(__builtin_ia32_bzhi_di)
else if constexpr (_S_size <= 64 && _Traits._M_have_bmi2())
return _S_init(__builtin_ia32_bzhi_di(~0ull >> (64 - _S_size), unsigned(__n)));
#endif
if constexpr (_S_size <= 32)
{
__glibcxx_simd_precondition(__n < 32, "invalid shift");
return _S_init(_Bitmask<_S_size>((1u << unsigned(__n)) - 1));
}
else if constexpr (_S_size <= 64)
{
__glibcxx_simd_precondition(__n < 64, "invalid shift");
return _S_init((1ull << unsigned(__n)) - 1);
}
else
static_assert(false);
}
}
[[__gnu__::__always_inline__]]
constexpr basic_mask&
_M_and_neighbors()
{
if constexpr (_S_use_bitmask)
_M_data &= ((_M_data >> 1) & 0x5555'5555'5555'5555ull)
| ((_M_data << 1) & ~0x5555'5555'5555'5555ull);
else
_M_data &= _VecOps<_DataType>::_S_swap_neighbors(_M_data);
return *this;
}
[[__gnu__::__always_inline__]]
constexpr basic_mask&
_M_or_neighbors()
{
if constexpr (_S_use_bitmask)
_M_data |= ((_M_data >> 1) & 0x5555'5555'5555'5555ull)
| ((_M_data << 1) & ~0x5555'5555'5555'5555ull);
else
_M_data |= _VecOps<_DataType>::_S_swap_neighbors(_M_data);
return *this;
}
template <typename _Mp>
[[__gnu__::__always_inline__]]
constexpr auto _M_chunk() const noexcept
{
constexpr int __n = _S_size / _Mp::_S_size;
constexpr int __rem = _S_size % _Mp::_S_size;
constexpr auto [...__is] = _IotaArray<__n>;
if constexpr (__rem == 0)
return array<_Mp, __n>{__extract_simd_at<_Mp>(cw<_Mp::_S_size * __is>, *this)...};
else
{
using _Rest = resize_t<__rem, _Mp>;
return tuple(__extract_simd_at<_Mp>(cw<_Mp::_S_size * __is>, *this)...,
__extract_simd_at<_Rest>(cw<_Mp::_S_size * __n>, *this));
}
}
[[__gnu__::__always_inline__]]
static constexpr const basic_mask&
_S_concat(const basic_mask& __x0) noexcept
{ return __x0; }
template <typename... _As>
requires (sizeof...(_As) > 1)
[[__gnu__::__always_inline__]]
static constexpr basic_mask
_S_concat(const basic_mask<_Bytes, _As>&... __xs) noexcept
{
static_assert(_S_size == (_As::_S_size + ...));
return __extract_simd_at<basic_mask>(cw<0>, __xs...);
}
// [simd.mask.overview] default constructor -----------------------------
basic_mask() = default;
// [simd.mask.overview] conversion extensions ---------------------------
[[__gnu__::__always_inline__]]
constexpr
basic_mask(_DataType __x) requires(!_S_is_scalar && !_S_use_bitmask)
: _M_data(__x)
{}
[[__gnu__::__always_inline__]]
constexpr
operator _DataType() requires(!_S_is_scalar && !_S_use_bitmask)
{ return _M_data; }
// [simd.mask.ctor] broadcast constructor -------------------------------
[[__gnu__::__always_inline__]]
constexpr explicit
basic_mask(same_as<bool> auto __x) noexcept // LWG 4382.
: _M_data(__x ? _S_implicit_mask : _DataType())
{}
// [simd.mask.ctor] conversion constructor ------------------------------
template <size_t _UBytes, typename _UAbi>
requires (_S_size == _UAbi::_S_size)
[[__gnu__::__always_inline__]]
constexpr explicit(__is_mask_conversion_explicit<_Ap, _UAbi>(_Bytes, _UBytes))
basic_mask(const basic_mask<_UBytes, _UAbi>& __x) noexcept
: _M_data([&] [[__gnu__::__always_inline__]] {
using _UV = basic_mask<_UBytes, _UAbi>;
// bool to bool
if constexpr (_S_is_scalar)
return __x[0];
// converting from an "array of bool"
else if constexpr (_UV::_S_is_scalar)
{
constexpr auto [...__is] = _IotaArray<_S_size>;
if constexpr (_S_use_bitmask)
return ((_DataType(__x[__is]) << __is) | ...);
else
return _DataType{__vec_value_type<_DataType>(-__x[__is])...};
}
// vec-/bit-mask to bit-mask | bit-mask to vec-mask
else if constexpr (_S_use_bitmask || _UV::_S_use_bitmask)
return basic_mask(__x.to_bitset())._M_data;
// vec-mask to vec-mask
else if constexpr (_Bytes == _UBytes)
return _S_recursive_bit_cast(__x)._M_data;
else
{
#if _GLIBCXX_X86
// TODO: turn this into a __vec_mask_cast overload in simd_x86.h
if constexpr (_Bytes == 1 && _UBytes == 2)
if (!__is_const_known(__x))
{
if constexpr (_UAbi::_S_nreg == 1)
return __x86_cvt_vecmask<_DataType>(__x._M_data);
else if constexpr (_UAbi::_S_nreg == 2)
{
auto __lo = __x._M_data0._M_data;
auto __hi = __vec_zero_pad_to<sizeof(__lo)>(
__x._M_data1._M_concat_data());
return __x86_cvt_vecmask<_DataType>(__lo, __hi);
}
}
#endif
return __vec_mask_cast<_DataType>(__x._M_concat_data());
}
}())
{}
using _Base::_MaskBase;
// [simd.mask.ctor] generator constructor -------------------------------
template <__simd_generator_invokable<bool, _S_size> _Fp>
[[__gnu__::__always_inline__]]
constexpr explicit
basic_mask(_Fp&& __gen)
: _M_data([&] [[__gnu__::__always_inline__]] {
constexpr auto [...__is] = _IotaArray<_S_size>;
if constexpr (_S_is_scalar)
return __gen(__simd_size_c<0>);
else if constexpr (_S_use_bitmask)
return _DataType(((_DataType(__gen(__simd_size_c<__is>)) << __is)
| ...));
else
return _DataType{__vec_value_type<_DataType>(
__gen(__simd_size_c<__is>) ? -1 : 0)...};
}())
{}
// [simd.mask.ctor] bitset constructor ----------------------------------
[[__gnu__::__always_inline__]]
constexpr
basic_mask(const same_as<bitset<_S_size>> auto& __b) noexcept // LWG 4382.
: basic_mask(static_cast<_Bitmask<_S_size>>(__b.to_ullong()))
{
// more than 64 elements in one register? not yet.
static_assert(_S_size <= numeric_limits<unsigned long long>::digits);
}
// [simd.mask.ctor] uint constructor ------------------------------------
template <unsigned_integral _Tp>
requires (!same_as<_Tp, bool>) // LWG 4382.
[[__gnu__::__always_inline__]]
constexpr explicit
basic_mask(_Tp __val) noexcept
: _M_data([&] [[__gnu__::__always_inline__]] () {
if constexpr (_S_use_bitmask)
return __val;
else if constexpr (_S_is_scalar)
return bool(__val & 1);
else if (__is_const_known(__val))
{
constexpr auto [...__is] = _IotaArray<_S_size>;
return _DataType {__vec_value_type<_DataType>((__val & (1ull << __is)) == 0
? 0 : -1)...};
}
else
{
using _Ip = typename _VecType::value_type;
_VecType __v0 = _Ip(__val);
constexpr int __bits_per_element = sizeof(_Ip) * __CHAR_BIT__;
constexpr _VecType __pow2 = _VecType(cw<1>)
<< (__iota<_VecType> % cw<__bits_per_element>);
if constexpr (_S_size < __bits_per_element)
return ((__v0 & __pow2) > cw<0>)._M_concat_data();
else if constexpr (_S_size == __bits_per_element)
return ((__v0 & __pow2) != cw<0>)._M_concat_data();
else
{
static_assert(_Bytes == 1);
static_assert(sizeof(_Ip) == 1);
_Bitmask<_S_size> __bits = __val;
static_assert(sizeof(_VecType) % sizeof(__bits) == 0);
if constexpr (sizeof(_DataType) == 32)
{
__vec_builtin_type<_UInt<8>, 4> __v1 = {
0xffu & (__bits >> (0 * __CHAR_BIT__)),
0xffu & (__bits >> (1 * __CHAR_BIT__)),
0xffu & (__bits >> (2 * __CHAR_BIT__)),
0xffu & (__bits >> (3 * __CHAR_BIT__)),
};
__v1 *= 0x0101'0101'0101'0101ull;
__v0 = __builtin_bit_cast(_VecType, __v1);
return ((__v0 & __pow2) != cw<0>)._M_data;
}
else
{
using _V1 = vec<_Ip, sizeof(__bits)>;
_V1 __v1 = __builtin_bit_cast(_V1, __bits);
__v0 = _VecType::_S_static_permute(__v1, [](int __i) {
return __i / __CHAR_BIT__;
});
return ((__v0 & __pow2) != cw<0>)._M_data;
}
}
}
}())
{}
//Effects: Initializes the first M elements to the corresponding bit values in val, where M is
//the smaller of size() and the number of bits in the value representation
//([basic.types.general]) of the type of val. If M is less than size(), the remaining elements
//are initialized to zero.
// [simd.mask.subscr] ---------------------------------------------------
[[__gnu__::__always_inline__]]
constexpr value_type
operator[](__simd_size_type __i) const
{
__glibcxx_simd_precondition(__i >= 0 && __i < _S_size, "subscript is out of bounds");
if constexpr (_S_is_scalar)
return _M_data;
else if constexpr (_S_use_bitmask)
return bool((_M_data >> __i) & 1);
else
return _M_data[__i] & 1;
}
// [simd.mask.unary] ----------------------------------------------------
[[__gnu__::__always_inline__]]
constexpr basic_mask
operator!() const noexcept
{
if constexpr (_S_is_scalar)
return _S_init(!_M_data);
else
return _S_init(~_M_data);
}
[[__gnu__::__always_inline__]]
constexpr _VecType
operator+() const noexcept requires destructible<_VecType>
{ return operator _VecType(); }
constexpr _VecType
operator+() const noexcept = delete;
[[__gnu__::__always_inline__]]
constexpr _VecType
operator-() const noexcept requires destructible<_VecType>
{
using _Ip = typename _VecType::value_type;
if constexpr (_S_is_scalar)
return _Ip(-int(_M_data));
else if constexpr (_S_use_bitmask)
return __select_impl(*this, _Ip(-1), _Ip());
else
{
static_assert(sizeof(_VecType) == sizeof(_M_data));
return __builtin_bit_cast(_VecType, _M_data);
}
}
constexpr _VecType
operator-() const noexcept = delete;
[[__gnu__::__always_inline__]]
constexpr _VecType
operator~() const noexcept requires destructible<_VecType>
{
using _Ip = typename _VecType::value_type;
if constexpr (_S_is_scalar)
return _Ip(~int(_M_data));
else if constexpr (_S_use_bitmask)
return __select_impl(*this, _Ip(-2), _Ip(-1));
else
{
static_assert(sizeof(_VecType) == sizeof(_M_data));
return __builtin_bit_cast(_VecType, _M_data) - _Ip(1);
}
}
constexpr _VecType
operator~() const noexcept = delete;
// [simd.mask.conv] -----------------------------------------------------
template <typename _Up, typename _UAbi>
requires (_UAbi::_S_size == _S_size)
[[__gnu__::__always_inline__]]
constexpr explicit(sizeof(_Up) != _Bytes)
operator basic_vec<_Up, _UAbi>() const noexcept
{
if constexpr (_S_is_scalar)
return _Up(_M_data);
else
{
using _UV = basic_vec<_Up, _UAbi>;
return __select_impl(static_cast<_UV::mask_type>(*this), _Up(1), _UV());
}
}
using _Base::operator basic_vec;
// [simd.mask.namedconv] ------------------------------------------------
[[__gnu__::__always_inline__]]
constexpr bitset<_S_size>
to_bitset() const noexcept
{
// more than 64 elements in one register? not yet.
static_assert(_S_size <= numeric_limits<unsigned long long>::digits);
return to_ullong();
}
/** @internal
* Return the mask as the smallest possible unsigned integer (up to 64 bits).
*
* @tparam _Offset Adjust the return type & value to start at bit @p _Offset.
* @tparam _Use_2_for_1 Store the value of every second element into one bit of the result.
* (precondition: each even/odd pair stores the same value)
*/
template <int _Offset = 0, _ArchTraits _Traits = {}>
[[__gnu__::__always_inline__]]
constexpr _Bitmask<_S_size + _Offset>
_M_to_uint() const
{
constexpr int __nbits = _S_size;
static_assert(__nbits + _Offset <= numeric_limits<unsigned long long>::digits);
// before shifting
using _U0 = _Bitmask<__nbits>;
// potentially wider type needed for shift by _Offset
using _Ur = _Bitmask<__nbits + _Offset>;
if constexpr (_S_is_scalar || _S_use_bitmask)
{
auto __bits = _M_data;
if constexpr (_S_is_partial)
__bits &= _S_implicit_mask;
return _Ur(__bits) << _Offset;
}
else
{
#if _GLIBCXX_X86
if (!__is_const_known(*this))
{
_U0 __uint;
if constexpr (_Bytes != 2) // movmskb would duplicate each bit
__uint = _U0(__x86_movmsk(_M_data));
else if constexpr (_Bytes == 2 && _Traits._M_have_bmi2())
__uint = __bit_extract_even<__nbits>(__x86_movmsk(_M_data));
else if constexpr (_Bytes == 2)
return __similar_mask<char, __nbits, _Ap>(*this).template _M_to_uint<_Offset>();
else
static_assert(false);
// TODO: with AVX512 use __builtin_ia32_cvt[bwdq]2mask(128|256|512)
// TODO: Ask for compiler builtin to do the best of the above. This should also
// combine with a preceding vector-mask compare to produce a bit-mask compare (on
// AVX512)
if constexpr (_S_is_partial)
__uint &= (_U0(1) << _S_size) - 1;
return _Ur(__uint) << _Offset;
}
#endif
using _IV = _VecType;
static_assert(destructible<_IV>);
const typename _IV::mask_type& __k = [&] [[__gnu__::__always_inline__]] () {
if constexpr (is_same_v<typename _IV::mask_type, basic_mask>)
return *this;
else
return typename _IV::mask_type(*this);
}();
constexpr int __n = _IV::size();
if constexpr (_Bytes * __CHAR_BIT__ >= __n) // '1 << __iota' cannot overflow
{ // reduce(select(k, powers_of_2, 0))
constexpr _IV __pow2 = _IV(cw<1>) << __iota<_IV>;
return _Ur(_U0(__select_impl(__k, __pow2, _IV())
._M_reduce(bit_or<>()))) << _Offset;
}
else if constexpr (__n % __CHAR_BIT__ != 0)
{ // recurse after splitting in two
constexpr int __n_lo = __n - __n % __CHAR_BIT__;
const auto [__lo, __hi] = chunk<__n_lo>(__k);
_Ur __bits = __hi.template _M_to_uint<_Offset + __n_lo>();
return __bits | __lo.template _M_to_uint<_Offset>();
}
else
{ // limit powers_of_2 to 1, 2, 4, ..., 128
constexpr _IV __pow2 = _IV(cw<1>) << (__iota<_IV> % _IV(cw<__CHAR_BIT__>));
_IV __x = __select_impl(__k, __pow2, _IV());
// partial reductions of 8 neighboring elements
__x |= _IV::_S_static_permute(__x, _SwapNeighbors<4>());
__x |= _IV::_S_static_permute(__x, _SwapNeighbors<2>());
__x |= _IV::_S_static_permute(__x, _SwapNeighbors<1>());
// permute partial reduction results to the front
__x = _IV::_S_static_permute(__x, [](int __i) {
return __i * 8 < __n ? __i * 8 : uninit_element;
});
// extract front as scalar unsigned
_U0 __bits = __builtin_bit_cast(
__similar_vec<_U0, __n * _Bytes / sizeof(_U0), _Ap>, __x)[0];
// mask off unused bits
if constexpr (!__has_single_bit(unsigned(__nbits)))
__bits &= (_U0(1) << __nbits) - 1;
return _Ur(__bits) << _Offset;
}
}
}
[[__gnu__::__always_inline__]]
constexpr unsigned long long
to_ullong() const
{ return _M_to_uint(); }
// [simd.mask.binary] ---------------------------------------------------
[[__gnu__::__always_inline__]]
friend constexpr basic_mask
operator&&(const basic_mask& __x, const basic_mask& __y) noexcept
{ return _S_init(__x._M_data & __y._M_data); }
[[__gnu__::__always_inline__]]
friend constexpr basic_mask
operator||(const basic_mask& __x, const basic_mask& __y) noexcept
{ return _S_init(__x._M_data | __y._M_data); }
[[__gnu__::__always_inline__]]
friend constexpr basic_mask
operator&(const basic_mask& __x, const basic_mask& __y) noexcept
{ return _S_init(__x._M_data & __y._M_data); }
[[__gnu__::__always_inline__]]
friend constexpr basic_mask
operator|(const basic_mask& __x, const basic_mask& __y) noexcept
{ return _S_init(__x._M_data | __y._M_data); }
[[__gnu__::__always_inline__]]
friend constexpr basic_mask
operator^(const basic_mask& __x, const basic_mask& __y) noexcept
{ return _S_init(__x._M_data ^ __y._M_data); }
// [simd.mask.cassign] --------------------------------------------------
[[__gnu__::__always_inline__]]
friend constexpr basic_mask&
operator&=(basic_mask& __x, const basic_mask& __y) noexcept
{
__x._M_data &= __y._M_data;
return __x;
}
[[__gnu__::__always_inline__]]
friend constexpr basic_mask&
operator|=(basic_mask& __x, const basic_mask& __y) noexcept
{
__x._M_data |= __y._M_data;
return __x;
}
[[__gnu__::__always_inline__]]
friend constexpr basic_mask&
operator^=(basic_mask& __x, const basic_mask& __y) noexcept
{
__x._M_data ^= __y._M_data;
return __x;
}
// [simd.mask.comparison] -----------------------------------------------
[[__gnu__::__always_inline__]]
friend constexpr basic_mask
operator==(const basic_mask& __x, const basic_mask& __y) noexcept
{ return !(__x ^ __y); }
[[__gnu__::__always_inline__]]
friend constexpr basic_mask
operator!=(const basic_mask& __x, const basic_mask& __y) noexcept
{ return __x ^ __y; }
[[__gnu__::__always_inline__]]
friend constexpr basic_mask
operator>=(const basic_mask& __x, const basic_mask& __y) noexcept
{ return __x || !__y; }
[[__gnu__::__always_inline__]]
friend constexpr basic_mask
operator<=(const basic_mask& __x, const basic_mask& __y) noexcept
{ return !__x || __y; }
[[__gnu__::__always_inline__]]
friend constexpr basic_mask
operator>(const basic_mask& __x, const basic_mask& __y) noexcept
{ return __x && !__y; }
[[__gnu__::__always_inline__]]
friend constexpr basic_mask
operator<(const basic_mask& __x, const basic_mask& __y) noexcept
{ return !__x && __y; }
// [simd.mask.cond] -----------------------------------------------------
[[__gnu__::__always_inline__]]
friend constexpr basic_mask
__select_impl(const basic_mask& __k, const basic_mask& __t, const basic_mask& __f) noexcept
{
if constexpr (!_S_use_bitmask)
{
#if _GLIBCXX_X86
// this works around bad code-gen when the compiler can't see that __k is a vector-mask.
// This pattern, is recognized to match the x86 blend instructions, which only consider
// the sign bit of the mask register. Also, without SSE4, if the compiler knows that __k
// is a vector-mask, then the '< 0' is elided.
return __k._M_data < 0 ? __t._M_data : __f._M_data;
#endif
return __k._M_data ? __t._M_data : __f._M_data;
}
else
return (__k._M_data & __t._M_data) | (~__k._M_data & __f._M_data);
}
[[__gnu__::__always_inline__]]
friend constexpr basic_mask
__select_impl(const basic_mask& __k, same_as<bool> auto __t, same_as<bool> auto __f) noexcept
{
if (__t == __f)
return basic_mask(__t);
else
return __t ? __k : !__k;
}
template <__vectorizable _T0, same_as<_T0> _T1>
requires (sizeof(_T0) == _Bytes)
[[__gnu__::__always_inline__]]
friend constexpr vec<_T0, _S_size>
__select_impl(const basic_mask& __k, const _T0& __t, const _T1& __f) noexcept
{
if constexpr (_S_is_scalar)
return __k._M_data ? __t : __f;
else
{
using _Vp = vec<_T0, _S_size>;
using _Mp = typename _Vp::mask_type;
return __select_impl(_Mp(__k), _Vp(__t), _Vp(__f));
}
}
// [simd.mask.reductions] implementation --------------------------------
[[__gnu__::__always_inline__]]
constexpr bool
_M_all_of() const noexcept
{
if constexpr (_S_is_scalar)
return _M_data;
else if constexpr (_S_use_bitmask)
{
if constexpr (_S_is_partial)
// PR120925 (partial kortest pattern not recognized)
return (_M_data & _S_implicit_mask) == _S_implicit_mask;
else
return _M_data == _S_implicit_mask;
}
#if _GLIBCXX_X86
else if (!__is_const_known(_M_data))
return __x86_vecmask_all<_S_size>(_M_data);
#endif
else
return _VecOps<_DataType, _S_size>::_S_all_of(_M_data);
}
[[__gnu__::__always_inline__]]
constexpr bool
_M_any_of() const noexcept
{
if constexpr (_S_is_scalar)
return _M_data;
else if constexpr (_S_use_bitmask)
{
if constexpr (_S_is_partial)
// PR120925 (partial kortest pattern not recognized)
return (_M_data & _S_implicit_mask) != 0;
else
return _M_data != 0;
}
#if _GLIBCXX_X86
else if (!__is_const_known(_M_data))
return __x86_vecmask_any<_S_size>(_M_data);
#endif
else
return _VecOps<_DataType, _S_size>::_S_any_of(_M_data);
}
[[__gnu__::__always_inline__]]
constexpr bool
_M_none_of() const noexcept
{
if constexpr (_S_is_scalar)
return !_M_data;
else if constexpr (_S_use_bitmask)
{
if constexpr (_S_is_partial)
// PR120925 (partial kortest pattern not recognized)
return (_M_data & _S_implicit_mask) == 0;
else
return _M_data == 0;
}
#if _GLIBCXX_X86
else if (!__is_const_known(_M_data))
return __x86_vecmask_none<_S_size>(_M_data);
#endif
else
return _VecOps<_DataType, _S_size>::_S_none_of(_M_data);
}
[[__gnu__::__always_inline__]]
constexpr __simd_size_type
_M_reduce_count() const noexcept
{
if constexpr (_S_is_scalar)
return int(_M_data);
else if constexpr (_S_size <= numeric_limits<unsigned>::digits)
return __builtin_popcount(_M_to_uint());
else
return __builtin_popcountll(to_ullong());
}
[[__gnu__::__always_inline__]]
constexpr __simd_size_type
_M_reduce_min_index() const
{
const auto __bits = _M_to_uint();
__glibcxx_simd_precondition(__bits, "An empty mask does not have a min_index.");
if constexpr (_S_size == 1)
return 0;
else
return __countr_zero(__bits);
}
[[__gnu__::__always_inline__]]
constexpr __simd_size_type
_M_reduce_max_index() const
{
const auto __bits = _M_to_uint();
__glibcxx_simd_precondition(__bits, "An empty mask does not have a max_index.");
if constexpr (_S_size == 1)
return 0;
else
return __highest_bit(__bits);
}
[[__gnu__::__always_inline__]]
friend constexpr bool
__is_const_known(const basic_mask& __x)
{ return __builtin_constant_p(__x._M_data); }
};
template <size_t _Bytes, __abi_tag _Ap>
requires (_Ap::_S_nreg > 1)
class basic_mask<_Bytes, _Ap>
: public _MaskBase<_Bytes, _Ap>
{
using _Base = _MaskBase<_Bytes, _Ap>;
using _VecType = _Base::_VecType;
template <size_t, typename>
friend class basic_mask;
template <typename, typename>
friend class basic_vec;
static constexpr int _S_size = _Ap::_S_size;
static constexpr int _N0 = __bit_ceil(unsigned(_S_size)) / 2;
static constexpr int _N1 = _S_size - _N0;
static constexpr int _Nreg0 = __bit_ceil(unsigned(_Ap::_S_nreg)) / 2;
static constexpr int _Nreg1 = _Ap::_S_nreg - _Nreg0;
// explicitly request _Nreg0 rather than use __abi_rebind. This way _Float16 can use half
// of native registers (since they convert to full float32 registers).
using _Abi0 = decltype(_Ap::template _S_resize<_N0, _Nreg0>());
using _Abi1 = decltype(_Ap::template _S_resize<_N1, _Nreg1>());
using _Mask0 = basic_mask<_Bytes, _Abi0>;
// the implementation (and users) depend on elements being contiguous in memory
static_assert(_Mask0::_S_padding_bytes == 0 && !_Mask0::_S_is_partial);
using _Mask1 = basic_mask<_Bytes, _Abi1>;
static constexpr bool _S_is_partial = _Mask1::_S_is_partial;
// _Ap::_S_nreg determines how deep the recursion goes. E.g. basic_mask<4, _Abi<8, 4>> cannot
// use basic_mask<4, _Abi<4, 1>> as _Mask0/1 types.
static_assert(_Mask0::abi_type::_S_nreg + _Mask1::abi_type::_S_nreg == _Ap::_S_nreg);
static constexpr bool _S_use_bitmask = _Mask0::_S_use_bitmask;
static constexpr bool _S_is_scalar = _Mask0::_S_is_scalar;
_Mask0 _M_data0;
_Mask1 _M_data1;
static constexpr bool _S_has_bool_member = _Mask1::_S_has_bool_member;
// by construction _N0 >= _N1
// => sizeof(_Mask0) >= sizeof(_Mask1)
// and __alignof__(_Mask0) >= __alignof__(_Mask1)
static constexpr size_t _S_padding_bytes
= (__alignof__(_Mask0) == __alignof__(_Mask1)
? 0 : __alignof__(_Mask0) - (sizeof(_Mask1) % __alignof__(_Mask0)))
+ _Mask1::_S_padding_bytes;
public:
using value_type = bool;
using abi_type = _Ap;
using iterator = _Base::iterator;
using const_iterator = _Base::const_iterator;
[[__gnu__::__always_inline__]]
static constexpr basic_mask
_S_init(const _Mask0& __x, const _Mask1& __y)
{
basic_mask __r;
__r._M_data0 = __x;
__r._M_data1 = __y;
return __r;
}
[[__gnu__::__always_inline__]]
static constexpr basic_mask
_S_init(unsigned_integral auto __bits)
{ return basic_mask(__bits); }
template <typename _U0, typename _U1>
[[__gnu__::__always_inline__]]
static constexpr basic_mask
_S_init(const __trivial_pair<_U0, _U1>& __bits)
{
if constexpr (is_unsigned_v<_U0>)
{
static_assert(is_unsigned_v<_U1>);
return _S_init(_Mask0(__bits._M_first), _Mask1(__bits._M_second));
}
else if constexpr (is_unsigned_v<_U1>)
return _S_init(_Mask0::_S_init(__bits._M_first), _Mask1(__bits._M_second));
else
return _S_init(_Mask0::_S_init(__bits._M_first), _Mask1::_S_init(__bits._M_second));
}
[[__gnu__::__always_inline__]]
constexpr const _Mask0&
_M_get_low() const
{ return _M_data0; }
[[__gnu__::__always_inline__]]
constexpr const _Mask1&
_M_get_high() const
{ return _M_data1; }
template <size_t _UBytes, typename _UAbi>
[[__gnu__::__always_inline__]]
static constexpr basic_mask
_S_recursive_bit_cast(const basic_mask<_UBytes, _UAbi>& __x)
{
using _Mp = basic_mask<_UBytes, _UAbi>;
if constexpr (_Mp::_S_has_bool_member || sizeof(basic_mask) > sizeof(__x)
|| _Mp::_S_padding_bytes != 0)
return _S_init(__builtin_bit_cast(_Mask0, __x._M_data0),
_Mask1::_S_recursive_bit_cast(__x._M_data1));
else if constexpr (sizeof(basic_mask) == sizeof(__x))
return __builtin_bit_cast(basic_mask, __x);
else
{ // e.g. on IvyBridge (different alignment => different sizeof)
struct _Tmp { alignas(_Mp) basic_mask _M_data; };
return __builtin_bit_cast(_Tmp, __x)._M_data;
}
}
[[__gnu__::__always_inline__]]
constexpr auto
_M_concat_data(bool __do_sanitize = _S_is_partial) const
{
if constexpr (_S_use_bitmask)
{
static_assert(_S_size <= numeric_limits<unsigned long long>::digits,
"cannot concat more than 64 bits");
using _Up = _Bitmask<_S_size>;
return _Up(_M_data0._M_concat_data() | (_Up(_M_data1._M_concat_data(__do_sanitize)) << _N0));
}
else
{
auto __lo = _M_data0._M_concat_data();
auto __hi = __vec_zero_pad_to<sizeof(__lo)>(_M_data1._M_concat_data(__do_sanitize));
return __vec_concat(__lo, __hi);
}
}
template <_ArchTraits _Traits = {}>
[[__gnu__::__always_inline__]]
static constexpr basic_mask
_S_partial_mask_of_n(int __n)
{
#if __has_builtin(__builtin_ia32_bzhi_di)
if constexpr (_S_use_bitmask && _S_size <= 64 && _Traits._M_have_bmi2())
return basic_mask(__builtin_ia32_bzhi_di(~0ull >> (64 - _S_size), unsigned(__n)));
#endif
if constexpr (_N0 == 1)
{
static_assert(_S_size == 2); // => __n == 1
return _S_init(_Mask0(true), _Mask1(false));
}
else if (__n < _N0)
return _S_init(_Mask0::_S_partial_mask_of_n(__n), _Mask1(false));
else if (__n == _N0 || _N1 == 1)
return _S_init(_Mask0(true), _Mask1(false));
else if constexpr (_N1 != 1)
return _S_init(_Mask0(true), _Mask1::_S_partial_mask_of_n(__n - _N0));
}
[[__gnu__::__always_inline__]]
constexpr basic_mask&
_M_and_neighbors()
{
_M_data0._M_and_neighbors();
_M_data1._M_and_neighbors();
return *this;
}
[[__gnu__::__always_inline__]]
constexpr basic_mask&
_M_or_neighbors()
{
_M_data0._M_or_neighbors();
_M_data1._M_or_neighbors();
return *this;
}
template <typename _Mp>
[[__gnu__::__always_inline__]]
constexpr auto
_M_chunk() const noexcept
{
constexpr int __n = _S_size / _Mp::_S_size;
constexpr int __rem = _S_size % _Mp::_S_size;
constexpr auto [...__is] = _IotaArray<__n>;
if constexpr (__rem == 0)
return array<_Mp, __n>{__extract_simd_at<_Mp>(cw<_Mp::_S_size * __is>,
_M_data0, _M_data1)...};
else
{
using _Rest = resize_t<__rem, _Mp>;
return tuple(__extract_simd_at<_Mp>(cw<_Mp::_S_size * __is>, _M_data0, _M_data1)...,
__extract_simd_at<_Rest>(cw<_Mp::_S_size * __n>, _M_data0, _M_data1));
}
}
[[__gnu__::__always_inline__]]
static constexpr basic_mask
_S_concat(const basic_mask& __x0) noexcept
{ return __x0; }
template <typename... _As>
requires (sizeof...(_As) >= 2)
[[__gnu__::__always_inline__]]
static constexpr basic_mask
_S_concat(const basic_mask<_Bytes, _As>&... __xs) noexcept
{
static_assert(_S_size == (_As::_S_size + ...));
return _S_init(__extract_simd_at<_Mask0>(cw<0>, __xs...),
__extract_simd_at<_Mask1>(cw<_N0>, __xs...));
}
// [simd.mask.overview] default constructor -----------------------------
basic_mask() = default;
// [simd.mask.overview] conversion extensions ---------------------------
// TODO: any?
// [simd.mask.ctor] broadcast constructor -------------------------------
[[__gnu__::__always_inline__]]
constexpr explicit
basic_mask(same_as<bool> auto __x) noexcept // LWG 4382.
: _M_data0(__x), _M_data1(__x)
{}
// [simd.mask.ctor] conversion constructor ------------------------------
template <size_t _UBytes, typename _UAbi>
requires (_S_size == _UAbi::_S_size)
[[__gnu__::__always_inline__]]
constexpr explicit(__is_mask_conversion_explicit<_Ap, _UAbi>(_Bytes, _UBytes))
basic_mask(const basic_mask<_UBytes, _UAbi>& __x) noexcept
: _M_data0([&] {
if constexpr (_UAbi::_S_nreg > 1)
{
return __x._M_data0;
}
else if constexpr (_N0 == 1)
return _Mask0(__x[0]);
else
return get<0>(chunk<_N0>(__x));
}()),
_M_data1([&] {
if constexpr (_UAbi::_S_nreg > 1)
{
return __x._M_data1;
}
else if constexpr (_N1 == 1)
return _Mask1(__x[_N0]);
else
return get<1>(chunk<_N0>(__x));
}())
{}
using _Base::_MaskBase;
// [simd.mask.ctor] generator constructor -------------------------------
template <__simd_generator_invokable<bool, _S_size> _Fp>
[[__gnu__::__always_inline__]]
constexpr explicit
basic_mask(_Fp&& __gen)
: _M_data0(__gen), _M_data1([&] [[__gnu__::__always_inline__]] (auto __i) {
return __gen(__simd_size_c<__i + _N0>);
})
{}
// [simd.mask.ctor] bitset constructor ----------------------------------
[[__gnu__::__always_inline__]]
constexpr
basic_mask(const same_as<bitset<_S_size>> auto& __b) noexcept // LWG 4382.
: _M_data0(__bitset_split<_N0>(__b)._M_lo), _M_data1(__bitset_split<_N0>(__b)._M_hi)
{}
// [simd.mask.ctor] uint constructor ------------------------------------------
template <unsigned_integral _Tp>
requires (!same_as<_Tp, bool>) // LWG 4382.
[[__gnu__::__always_inline__]]
constexpr explicit
basic_mask(_Tp __val) noexcept
: _M_data0(static_cast<_Bitmask<_N0>>(__val)),
_M_data1(sizeof(_Tp) * __CHAR_BIT__ > _N0
? static_cast<_Bitmask<_N1>>(__val >> _N0) : _Bitmask<_N1>())
{}
// [simd.mask.subscr] ---------------------------------------------------
[[__gnu__::__always_inline__]]
constexpr value_type
operator[](__simd_size_type __i) const
{
__glibcxx_simd_precondition(__i >= 0 && __i < _S_size, "subscript is out of bounds");
if (__is_const_known(__i))
return __i < _N0 ? _M_data0[__i] : _M_data1[__i - _N0];
else if constexpr (_M_data1._S_has_bool_member)
// in some cases the last element can be 'bool' instead of bit-/vector-mask;
// e.g. mask<short, 17> is {mask<short, 16>, mask<short, 1>}, where the latter uses
// _ScalarAbi<1>, which is stored as 'bool'
return __i < _N0 ? _M_data0[__i] : _M_data1[__i - _N0];
else if constexpr (abi_type::_S_is_bitmask)
{
using _AliasingByte [[__gnu__::__may_alias__]] = unsigned char;
return bool((reinterpret_cast<const _AliasingByte*>(this)
[__i / __CHAR_BIT__] >> (__i % __CHAR_BIT__)) & 1);
}
else
{
using _AliasingInt [[__gnu__::__may_alias__]] = __integer_from<_Bytes>;
return reinterpret_cast<const _AliasingInt*>(this)[__i] != 0;
}
}
// [simd.mask.unary] ----------------------------------------------------
[[__gnu__::__always_inline__]]
constexpr basic_mask
operator!() const noexcept
{ return _S_init(!_M_data0, !_M_data1); }
[[__gnu__::__always_inline__]]
constexpr _VecType
operator+() const noexcept requires destructible<_VecType>
{ return _VecType::_S_concat(+_M_data0, +_M_data1); }
constexpr _VecType
operator+() const noexcept = delete;
[[__gnu__::__always_inline__]]
constexpr _VecType
operator-() const noexcept requires destructible<_VecType>
{ return _VecType::_S_concat(-_M_data0, -_M_data1); }
constexpr _VecType
operator-() const noexcept = delete;
[[__gnu__::__always_inline__]]
constexpr _VecType
operator~() const noexcept requires destructible<_VecType>
{ return _VecType::_S_concat(~_M_data0, ~_M_data1); }
constexpr _VecType
operator~() const noexcept = delete;
// [simd.mask.conv] -----------------------------------------------------
template <typename _Up, typename _UAbi>
requires (_UAbi::_S_size == _S_size)
[[__gnu__::__always_inline__]]
constexpr explicit(sizeof(_Up) != _Bytes)
operator basic_vec<_Up, _UAbi>() const noexcept
{
using _Rp = basic_vec<_Up, _UAbi>;
return _Rp::_S_init(static_cast<_Rp::_DataType0>(_M_data0),
static_cast<_Rp::_DataType1>(_M_data1));
}
using _Base::operator basic_vec;
// [simd.mask.namedconv] ------------------------------------------------
[[__gnu__::__always_inline__]]
constexpr bitset<_S_size>
to_bitset() const noexcept
{
if constexpr (_S_size <= numeric_limits<unsigned long long>::digits)
return to_ullong();
else
{
static_assert(_N0 % numeric_limits<unsigned long long>::digits == 0);
struct _Tmp
{
bitset<_N0> _M_lo;
bitset<_N1> _M_hi;
} __tmp = {_M_data0.to_bitset(), _M_data1.to_bitset()};
return __builtin_bit_cast(bitset<_S_size>, __tmp);
}
}
template <int _Offset = 0, _ArchTraits _Traits = {}>
[[__gnu__::__always_inline__]]
constexpr auto
_M_to_uint() const
{
constexpr int _N0x = _N0;
if constexpr (_N0x >= numeric_limits<unsigned long long>::digits)
{
static_assert(_Offset == 0);
return __trivial_pair {
_M_data0.template _M_to_uint<0>(),
_M_data1.template _M_to_uint<0>()
};
}
else
{
#if _GLIBCXX_X86
if constexpr (_Bytes == 2 && !_Traits._M_have_bmi2() && _Ap::_S_nreg == 2
&& !_S_use_bitmask)
return __similar_mask<char, _S_size, _Ap>(*this).template _M_to_uint<_Offset>();
#endif
auto __uint = _M_data1.template _M_to_uint<_N0x + _Offset>();
__uint |= _M_data0.template _M_to_uint<_Offset>();
return __uint;
}
}
[[__gnu__::__always_inline__]]
constexpr unsigned long long
to_ullong() const
{
if constexpr (_S_size <= numeric_limits<unsigned long long>::digits)
return _M_to_uint();
else
{
__glibcxx_simd_precondition(_M_data1.to_ullong() == 0,
"to_ullong called on mask with 'true' elements at indices"
"higher than representable in a ullong");
return _M_data0.to_ullong();
}
}
// [simd.mask.binary]
[[__gnu__::__always_inline__]]
friend constexpr basic_mask
operator&&(const basic_mask& __x, const basic_mask& __y) noexcept
{ return _S_init(__x._M_data0 && __y._M_data0, __x._M_data1 && __y._M_data1); }
[[__gnu__::__always_inline__]]
friend constexpr basic_mask
operator||(const basic_mask& __x, const basic_mask& __y) noexcept
{ return _S_init(__x._M_data0 || __y._M_data0, __x._M_data1 || __y._M_data1); }
[[__gnu__::__always_inline__]]
friend constexpr basic_mask
operator&(const basic_mask& __x, const basic_mask& __y) noexcept
{ return _S_init(__x._M_data0 & __y._M_data0, __x._M_data1 & __y._M_data1); }
[[__gnu__::__always_inline__]]
friend constexpr basic_mask
operator|(const basic_mask& __x, const basic_mask& __y) noexcept
{ return _S_init(__x._M_data0 | __y._M_data0, __x._M_data1 | __y._M_data1); }
[[__gnu__::__always_inline__]]
friend constexpr basic_mask
operator^(const basic_mask& __x, const basic_mask& __y) noexcept
{ return _S_init(__x._M_data0 ^ __y._M_data0, __x._M_data1 ^ __y._M_data1); }
// [simd.mask.cassign]
[[__gnu__::__always_inline__]]
friend constexpr basic_mask&
operator&=(basic_mask& __x, const basic_mask& __y) noexcept
{
__x._M_data0 &= __y._M_data0;
__x._M_data1 &= __y._M_data1;
return __x;
}
[[__gnu__::__always_inline__]]
friend constexpr basic_mask&
operator|=(basic_mask& __x, const basic_mask& __y) noexcept
{
__x._M_data0 |= __y._M_data0;
__x._M_data1 |= __y._M_data1;
return __x;
}
[[__gnu__::__always_inline__]]
friend constexpr basic_mask&
operator^=(basic_mask& __x, const basic_mask& __y) noexcept
{
__x._M_data0 ^= __y._M_data0;
__x._M_data1 ^= __y._M_data1;
return __x;
}
// [simd.mask.comparison] -----------------------------------------------
[[__gnu__::__always_inline__]]
friend constexpr basic_mask
operator==(const basic_mask& __x, const basic_mask& __y) noexcept
{ return !(__x ^ __y); }
[[__gnu__::__always_inline__]]
friend constexpr basic_mask
operator!=(const basic_mask& __x, const basic_mask& __y) noexcept
{ return __x ^ __y; }
[[__gnu__::__always_inline__]]
friend constexpr basic_mask
operator>=(const basic_mask& __x, const basic_mask& __y) noexcept
{ return __x || !__y; }
[[__gnu__::__always_inline__]]
friend constexpr basic_mask
operator<=(const basic_mask& __x, const basic_mask& __y) noexcept
{ return !__x || __y; }
[[__gnu__::__always_inline__]]
friend constexpr basic_mask
operator>(const basic_mask& __x, const basic_mask& __y) noexcept
{ return __x && !__y; }
[[__gnu__::__always_inline__]]
friend constexpr basic_mask
operator<(const basic_mask& __x, const basic_mask& __y) noexcept
{ return !__x && __y; }
// [simd.mask.cond] -----------------------------------------------------
[[__gnu__::__always_inline__]]
friend constexpr basic_mask
__select_impl(const basic_mask& __k, const basic_mask& __t, const basic_mask& __f) noexcept
{
return _S_init(__select_impl(__k._M_data0, __t._M_data0, __f._M_data0),
__select_impl(__k._M_data1, __t._M_data1, __f._M_data1));
}
[[__gnu__::__always_inline__]]
friend constexpr basic_mask
__select_impl(const basic_mask& __k, same_as<bool> auto __t, same_as<bool> auto __f) noexcept
{
if (__t == __f)
return basic_mask(__t);
else
return __t ? __k : !__k;
}
template <__vectorizable _T0, same_as<_T0> _T1>
requires (sizeof(_T0) == _Bytes)
[[__gnu__::__always_inline__]]
friend constexpr vec<_T0, _S_size>
__select_impl(const basic_mask& __k, const _T0& __t, const _T1& __f) noexcept
{
using _Vp = vec<_T0, _S_size>;
if constexpr (!is_same_v<basic_mask, typename _Vp::mask_type>)
return __select_impl(static_cast<_Vp::mask_type>(__k), __t, __f);
else
return _Vp::_S_init(__select_impl(__k._M_data0, __t, __f),
__select_impl(__k._M_data1, __t, __f));
}
template <_ArchTraits _Traits = {}>
[[__gnu__::__always_inline__]]
constexpr bool
_M_all_of() const
{
if constexpr (_N0 == _N1)
return (_M_data0 && _M_data1)._M_all_of();
else
return _M_data0._M_all_of() && _M_data1._M_all_of();
}
template <_ArchTraits _Traits = {}>
[[__gnu__::__always_inline__]]
constexpr bool
_M_any_of() const
{
if constexpr (_N0 == _N1)
return (_M_data0 || _M_data1)._M_any_of();
else
return _M_data0._M_any_of() || _M_data1._M_any_of();
}
template <_ArchTraits _Traits = {}>
[[__gnu__::__always_inline__]]
constexpr bool
_M_none_of() const
{
if constexpr (_N0 == _N1)
return (_M_data0 || _M_data1)._M_none_of();
else
return _M_data0._M_none_of() && _M_data1._M_none_of();
}
[[__gnu__::__always_inline__]]
constexpr __simd_size_type
_M_reduce_min_index() const
{
if constexpr (_S_size <= numeric_limits<unsigned long long>::digits)
{
const auto __bits = _M_to_uint();
__glibcxx_simd_precondition(__bits, "An empty mask does not have a min_index.");
if constexpr (_S_size == 1)
return 0;
else
return __countr_zero(_M_to_uint());
}
else if (_M_data0._M_none_of())
return _M_data1._M_reduce_min_index() + _N0;
else
return _M_data0._M_reduce_min_index();
}
[[__gnu__::__always_inline__]]
constexpr __simd_size_type
_M_reduce_max_index() const
{
if constexpr (_S_size <= numeric_limits<unsigned long long>::digits)
{
const auto __bits = _M_to_uint();
__glibcxx_simd_precondition(__bits, "An empty mask does not have a max_index.");
if constexpr (_S_size == 1)
return 0;
else
return __highest_bit(_M_to_uint());
}
else if (_M_data1._M_none_of())
return _M_data0._M_reduce_max_index();
else
return _M_data1._M_reduce_max_index() + _N0;
}
[[__gnu__::__always_inline__]]
friend constexpr bool
__is_const_known(const basic_mask& __x)
{ return __is_const_known(__x._M_data0) && __is_const_known(__x._M_data1); }
};
} // namespace simd
_GLIBCXX_END_NAMESPACE_VERSION
} // namespace std
#pragma GCC diagnostic pop
#endif // C++26
#endif // _GLIBCXX_SIMD_MASK_H
bit
bitset
std::chrono::operator<=
constexpr bool operator<=(const duration< _Rep1, _Period1 > &__lhs, const duration< _Rep2, _Period2 > &__rhs)
Definition chrono.h:859
std::chrono::operator>=
constexpr bool operator>=(const duration< _Rep1, _Period1 > &__lhs, const duration< _Rep2, _Period2 > &__rhs)
Definition chrono.h:873
std::chrono::operator<
constexpr bool operator<(const duration< _Rep1, _Period1 > &__lhs, const duration< _Rep2, _Period2 > &__rhs)
Definition chrono.h:826
std::chrono::operator>
constexpr bool operator>(const duration< _Rep1, _Period1 > &__lhs, const duration< _Rep2, _Period2 > &__rhs)
Definition chrono.h:866
std::operator-
constexpr complex< _Tp > operator-(const complex< _Tp > &__x, const complex< _Tp > &__y)
Return new complex value x minus y.
Definition complex:404
std::operator+
constexpr complex< _Tp > operator+(const complex< _Tp > &__x, const complex< _Tp > &__y)
Return new complex value x plus y.
Definition complex:374
std::end
_Tp * end(valarray< _Tp > &__va) noexcept
Return an iterator pointing to one past the last element of the valarray.
Definition valarray:1251
std::begin
_Tp * begin(valarray< _Tp > &__va) noexcept
Return an iterator pointing to the first element of the valarray.
Definition valarray:1229
std::max
constexpr const _Tp & max(const _Tp &, const _Tp &)
This does what you think it does.
Definition stl_algobase.h:256
std
ISO C++ entities toplevel namespace is std.
std::cend
constexpr auto cend(const _Container &__cont) noexcept(noexcept(std::end(__cont))) -> decltype(std::end(__cont))
Return an iterator pointing to one past the last element of the const container.
Definition range_access.h:144
std::size
constexpr auto size(const _Container &__cont) noexcept(noexcept(__cont.size())) -> decltype(__cont.size())
Return the size of a container.
Definition range_access.h:274
std::operator^
constexpr bitset< _Nb > operator^(const bitset< _Nb > &__x, const bitset< _Nb > &__y) noexcept
Global bitwise operations on bitsets.
Definition bitset:1638
std::cbegin
constexpr auto cbegin(const _Container &__cont) noexcept(noexcept(std::begin(__cont))) -> decltype(std::begin(__cont))
Return an iterator pointing to the first element of the const container.
Definition range_access.h:132
std::operator|
constexpr bitset< _Nb > operator|(const bitset< _Nb > &__x, const bitset< _Nb > &__y) noexcept
Global bitwise operations on bitsets.
Definition bitset:1628
std::operator&
constexpr bitset< _Nb > operator&(const bitset< _Nb > &__x, const bitset< _Nb > &__y) noexcept
Global bitwise operations on bitsets.
Definition bitset:1618
std::numeric_limits< unsigned long long >::digits
static constexpr int digits
Definition limits:218
std::numeric_limits::max
static constexpr _Tp max() noexcept
Definition limits:328
std::numeric_limits::min
static constexpr _Tp min() noexcept
Definition limits:324