1. Motivation
ISO/IEC 19570:2018 (1) introduced dataparallel types to the C++ Extensions
for Parallelism TS. [P1928R6] is proposing to make that a part of C++ IS.
Intel supports the concept of a standard interface to SIMD instruction
capabilities and we have made extra suggestions for other APIs and facilities for
in document [P2638R0]. One of the extra features we suggested was the
ability to work with interleaved complex values. It is now becoming common
for processor instruction sets to include native support for these operations
(e.g., Intel AVX512FP16, ARM Neon) and we think that allowing
to
directly access these features is advantageous.
This document gives more details about the motivation for supporting complexvalues, the different storage and implementation alternatives, and some suggestions for a suitable API.
2. Background
Complexvalued mathematics is widely used in scientific computing and many types
of engineering, with applications including physical simulations, wireless
signal processing, media processing, and much more besides. C++ already supports
complexvalued operations using the
template available in the
header file and C supports complex values by using the
keyword. C++ supports floating point elements, including
(C++23),
,
or
,
though in practice compilers permit integer complex values too. Such is the
importance of interleaved complex values that some mainstream processor vendors
now provide native hardware support for complexvalue SIMD instructions (e.g.,
Intel AVX512FP16, ARM Neon v8.3).
Any complex value is represented as a pair of values, one for the real component and one for the imaginary component. In C and C++ a complex value is a pair of two values of the appropriate data type, allocated to a single unit containing 2 elements which are stored contiguously. This is illustrated in the figure below. This storage layout is used in other languages too, allowing for easy data interchange between software written in different languages, or with comparable userdefined types. This format may also be used for hardware accelerators or interfaces too:
When many complex values are stored together, such as in a Cstyle array, a C++
or a C++
, then the real and imaginary elements are
interleaved in memory:
Many applications, libraries, programming languages and interfaces between diverse software and hardware components will store data in this interleaved format, and it is treated as the default layout for blocks of complexvalued data. To improve the compute efficiency of this data format both Intel and ARM have introduced native hardware support to allow efficient SIMD operations to be performed in this data format without having to resort to reformatting the data into a more SIMDamenable form. Where the underlying target hardware does not have native support for interleaved complex values it is straightforward to synthesize a sequence of operations which give the same effect.
Given the popularity of interleaved complexvalues as a data exchange and
compute format we propose that
should be able to directly represent
this simd format in a form exemplified as:
std :: simd < std :: complex < float > , 5 > std :: simd < std :: complex < double > , 9 >
In all these cases the fundamental element type will be a suitable complex value, and the individual components of each complex element will be worked on as a single unit, according to the rules of complex arithmetic where appropriate. This includes:

All numeric operators (e.g.,
,+
,/
,
) and their derivatives (e.g., assignment operators) will operate as per their standard complex type definition. For example, the multiply operator will invoke a complex multiply for each complex element.* 
Any numeric operation will apply the operation on a perelement basis, generating a simd object of the same size as the input, but modifying the element type to be the appropriate return type. For example, when applied to a
:simd < complex < T >> 
,exp
,sin
,log
,tan
and so on will generate acos simd < complex < T >> 
orabs
will generate anorm
(i.e., realvalued SIMD equivalent).simd < T >
Note that the mathematical behavior will also be applied appropriately (e.g., if a real or imaginary component is a NaN, then the complete operation for a complex operation will also be NaN).


The
associated with asimd_mask
will have each individual mask bit refer to a complete complex element, not to subcomponents of the complex elements. When thesimd < complex < T >>
is backed by a compact representation (e.g., AVX512), then each individual bit will refer to a complete complex element. When thesimd_mask
is backed by an element mask (i.e., multiple bits filling a container of the same size as the simd element) then the size of each bit element will be twice the size of the underlying complex value type (e.g., asimd_mask
would be equivalent to something likesimd_mask < complex < _Float16 >>
, sincesimd < uint32_t >
.sizeof ( uint32_t ) == 2 * sizeof ( _Float16 ) 
Operations or functions which are invalid for complex values will be removed. This includes relational operators (e.g.,
,<
,<=
,>
) and those derived from them such, as>=
ormin
.max 
Any operation which moves or reorders elements will move entire complex values. For example, any sort of permute, resize, split, concat, insert, extract or indexing operation works on complete complex elements as though they are atomic units.

Reduction operations apply at the level of complex values (this follows from the previous bullet), provided the mathematical operation is valid (e.g., reducemax would not be available).
One of the aims of
is to make it possible to write a generic
code, which can use either a scalar type or a dataparallel type
interchangeably. To this end,
provides overloads of many of the
standard functions which operate in dataparallel mode. The same should apply to
a simd of complex values; it should be possible to write an algorithm which uses
either
or
with only a type replacement,
not with major API changes. To this end
should include some member
functions, such as real or imag, to mirror those found in std::complex.
Note that an alternative way to create parallel complex values is to allow
to use simd elements, such as
.
This storage layout is called separated storage and discussion of this format is
outside the scope of this paper.
3. Overview of updates required to support std :: complex
simd elements
There are three ways in which
needs to change to accommodate
elements:

Modify the definition of vectorizable types and the operations on them to allow for complex elements. These modifications are small in nature and don’t change the fundamental
API.std :: simd 
Add a small number of methods to
to mimic those found instd :: simd
. These modifications allow astd :: complex
value to be easily inserted into source code by text or template replacement in place of astd ::: simd < complex < T >>
value without requiring a rewrite.std :: complex 
Add a set of overloads and free functions to allow
to reflect the different behaviors of complex (e.g.,simd < complex < T >>
,sin
,cos
,exp
).log
Each of these points will now be discussed in more detail.
3.1. std :: simd
base modifications
In its current definition,
allows the element type to be any
cvunqualified arithmetic type other than bool. The first modification is to
extend the set of allowable element types to include any type which is valid for
.
Any simd operation or function which relies only on an element being an atomic
container which can be arbitrarily moved or duplicated as a single unit will
continue to operate as expected. For example, all the following will work on
without modification:

Constructors or
/copy_to
functions for loading and storing values to and from contiguous iteratorscopy_from 
Broadcast constructor

Index operations

Reordering operations, such as
,simd_split
,simd_cat
,permute
,resize
andinsert
.extract 
Masking selection operations (using a mask to include or exclude values from an operation or copy).
’s of complex values do not need to have any special behaviour since the mask
is simply a collection of boolean values, and the underlying element type makes no difference.
Constructors, including generator functions, which work on atomic units (e.g., broadcasting constructor) will work without modification. Note that the existing broadcast constructor will already permit a realvalued object to be used in a broadcast, since an individual value may be converted to a complex value with a zero imaginary:
auto x = simd < std :: complex < float >> ( 3.14f ); // [3.14 + 0i, 3.14 + 0i, ...]
Numeric operations (binary, unary, and compoundassignment) are mostly covered by [P1928R6] remark:
A simd object initialized with the results of applying the indicated operator to
and
as a binary elementwise operation
In the case of complex values this has the practical effect of ensuring that
operators will perform their complex operations without changing the definition
of
(e.g.,
will perform complexvalued
multiplication). Furthermore, [P1928R6] notes that constraints will be applied
as necessary on operators and functions:
Constraints: Application of the indicated operator to objects of type
is wellformed.
This will ensure that operators which are not permitted for complex numbers
(e.g.,
,
,
) will be removed from
the overload set, as will functions which rely on these operators (e.g.,
,
,
).
3.2. Additional complex methods for std :: simd
It is desirable for
to be source or templatereplaceable with
respect to its underlying element type. For example, given a simple code
fragment which works with
:
auto my_fn ( auto cmplx_value ) { std :: complex < float > rotator ( 0 , 1 ); return ( cmplx_value * rotator ). real (); }
In this example the function will work with a
input. Ideally
it should also work with an input of type
. For this to happen,
firstly the
must work with a scalar value, and secondly it should be
possible for the
method to be invoked on a
value.
provides a number of member functions:

Constructors

(assignment)operator = 
,operator +=
,operator =
,operator *= operator /= 
real 
imag
The assignment operator and the compoundassignment operators are already
provided by
so no further action is required. The remaining member
functions that are required are considered in the following subsections.
3.2.1. Constructors
Conversion and copy methods already exist in
and can be used for
complex SIMD values. An additional constructor is needed to match the
constructor which can build a complex value from separate
real and imaginary components: constructed from real and imaginary simd values.
requires is_complex_number < T > constexpr basic_simd ( const basic_simd < T :: value_type , UAbi >& r = {}, const basic_simd < T :: value_type , UAbi >& i = {})
This constructor is constrained so that it can only be used for SIMD values with
complex elements (some suitable concept will be added to enforce this). Like its
counterpart it can be used to build a complex value from real
components only, with insertion of zero imaginaries. This constructor can be used
as in the following example:
simd < float , 8 > reals = ...; simd < float , 8 > imaginaries = ...; // Create a realvalued complex number (i.e., zero imaginaries) simd < std :: complex < float > , 8 > realAsComplex ( reals ); // Create a complex value from real and imaginary simd inputs simd < std :: complex < float > , 8 > cmplx ( reals , imaginaries );
3.2.2. Real/imag accessors
It should be possible to separately read or write the real and imaginary
components. For example, given a simd of
values, the real
components of the simd will be extracted as a value of type
instead.
Similarly, given a
value those values can be inserted into the simd
as the real components of each respective value.
The read accessors for
come in two variants: a free function,
and a member function. These would be defined in
as follows:
// Free functions for accessing real/imaginary components template < typename T , typename ABI > constexpr auto real ( const basic_simd < complex < T > , ABI >& ); template < typename T , typename ABI > constexpr auto imag ( const basic_simd < complex < T > , ABI >& ); // Member functions of std::simd for accessing real/imaginary components. constexpr simd < T , size () > std :: simd < std :: complex < T >>:: real () const ; constexpr simd < T , size () > std :: simd < std :: complex < T >>:: imag () const ;
The write accessors for
are provided as member functions
which allow the real and imaginary components of an existing complex value to be
overwritten.
template < typename T , typename ABI > constexpr void real ( const basic_simd < T , ABI >& reals ); template < typename T , typename ABI > constexpr void imag ( const basic_simd < T , ABI >& reals );
The advantage of making
and
members of
is an API consistency
between
and
for both getters and setters
A disadvantage though is the
and
seem too specific to be provided for
.
Please see § 3.3.1 Alternative design for real and imag for more design considerations.
3.3. Additional free functions
The free functions for
include numeric operators, such as
,
,
and
. These are already proposed in [P1928R6] and need not be considered
further.
The remaining free functions which should be specialized for
include:
 returns the real part 

 returns the imaginary part 
 returns the magnitude of a complex number 
 returns the phase angle 
 returns the squared magnitude 
 returns the complex conjugate 
 returns the projection onto the Riemann sphere 
 constructs a complex number from magnitude and phase angle 
 complex base e exponential 
 complex natural logarithm with the branch cuts along the negative real axis 
 complex common logarithm with the branch cuts along the negative real axis 
 complex power, one or both arguments may be a complex number 
 complex square root in the range of the right halfplane 
 computes sine of a complex number 
 computes cosine of a complex number 
 computes tangent of a complex numbe 
 computes arc sine of a complex number 
 computes arc cosine of a complex number 
 computes arc tangent of a complex number 
 computes hyperbolic sine of a complex number 
 computes hyperbolic cosine of a complex number 
 computes hyperbolic tangent of a complex number 
 computes area hyperbolic sine of a complex number 
 computes area hyperbolic cosine of a complex number 
 computes area hyperbolic tangent of a complex number 
All of these functions should operate elementwise in the same way as their
scalar counterparts. Note that although most of these functions take complex
inputs and generate complex outputs, some of these functions generate
realvalued outputs (e.g.,
returns the magnitude, which is realvalued), or
accept an input which is realvalued (e.g.,
can accept a realvalued value
and return a complex value).
3.3.1. Alternative design for real
and imag
Instead of making
and
member functions we could provide them
only as free functions. Free function getters are already included in the
proposal, but the setters would be removed as member functions and replaced by
the following:
// Setters template < typename T , typename AbiC , typename AbiR > void real ( basic_simd < complex < T > , AbiC >& , basic_simd < T , AbiC > ); template < typename T , typename AbiC , typename AbiR > void imag ( basic_simd < complex < T > , AbiC >& , basic_simd < T , AbiR > );
The member function getters would also be removed.
The advantage of that approach is it keeps
specific functions outside of
class. The disadvantage though is inconsistency with existing
and
free functions for
, which allow reading only.
Please see § 3.2.2 Real/imag accessors for initial design.
We would like to hear the feedback of C++ standard committee what design looks more appropriate.
4. Implementation experience
Intel have written an example implementation of
which includes
the extensions to support interleaved complex values. We have been able to
generate efficient code both on targets with native support (e.g., AVX512_FP16),
and also on targets without native support which require synthesized code
sequences.
5. Wording
The wording relies on [P1928R6] being landed to the Working Draft. Below, substitute the � character with a number the editor finds appropriate for the table, paragraph, section or subsection.
5.1. Modify [simd.general]
�: The set of vectorizable types comprises all cvunqualified arithmetic types other than
std::complex<>
specializations.
.
5.2. Modify [simd.summary]
�: Default intialization performs no initialization of the elements
,
except when the value type is a std::complex<>
specialization in which case all
elements will be valueinitialized
; valueinitialization initializes each
element with
.
5.3. Modify [simd.ctor]
�constructors [simd.ctor]
simd
[...]
template < typename CAbi > constexpr explicit basic_simd ( const basic_simd < complex  element  type , CAbi > reals = {}, const basic_simd < complex  element  type , CAbi > imags = {}); Constraints:
The
is a complex value (i.e., some specialization of
value_type ).
std :: complex <> The
is the same as
complex  element  type (where T is the simd’s own
T :: value_type ).
value_type
is equal to
basic_simd < complex  element  type , CAbi >:: size .
basic_simd :: size Effects:
Construct a simd of elements of type
(where
value_type is a specialization of
value_type ), such that the i^{th} element is equal to the object constructed as
std :: complex <> for all i in the range
value_type ( reals [ i ], imags [ i ]) .
[ 0. . size ) Remarks:
Where no parameter values are provided this function acts as default initializer which valueinitializes all real and imaginary elements.
5.4. Add new section [simd.complex_accessors]
�complex accessors [simd.complex_accessors]
simd constexpr simd < complex  element  type , size > real () const ; constexpr simd < complex  element  type , size > imag () const ; Constraints:
is a complex value (i.e., any valid instantiation of
T template).
std :: complex <> Returns:
A simd with values of complexelementtype that is a value_type of
of the same width. The i^{th} element will be equal to
std :: complex or
( * this )[ i ]. real () respectively for all i in the range
( * this )[ i ]. imag () .
[ 0. . size ) template < typename CAbi > constexpr void real ( const basic_simd < complex  element  type , CAbi >& v ) noexcept ; template < typename CAbi > constexpr void imag ( const basic_simd < complex  element  type , CAbi >& v ) noexcept ; Constraints:
is a complex value (i.e., any valid instantiation of
T template).
std :: complex <>
is the same as the simd
complex  element  type .
value  type
is equal to
basic_simd < complex  element  type , CAbi >:: size .
basic_simd :: size Effect:
Modify each element of the simd such that the real or imaginary component of the i^{th} element is replaced by the value of
or
v [ i ]. real respectively.
v [ i ]. imag () template < typename T , typename Abi > constexpr simd < complex  element  type , size > real ( const basic_simd < typename T , typename Abi >& v ) noexcept ; template < typename T , typename Abi > constexpr simd < complex  element  type , size > imag ( const basic_simd < typename T , typename Abi >& v ) noexcept ; Constraints:
is a complex value (i.e., any valid instantiation of
T template).
std :: complex <> Returns:
A simd with values of complexelementtype that is a value_type of
of the same width, where the i^{th} element is equal to
std :: complex or
v [ i ]. real () respectively for all i in the range
v [ i ]. imag () .
[ 0. . v . size )
5.5. Add new section [simd.complex_math]
�complex maths library [simd.complex_library]
simd For each set of overloaded functions within
there shall be additional overloads sufficient to ensure that:
< complex >
Any
type can be used where
basic_simd < std :: complex < T > , Abi > would be used (parameter or return value).
std :: complex <> Any realvalued
type can be used where T would be used (parameter or return value).
simd < T , Abi > It is unspecified whether a call to these overloads with arguments that are all convertible to
but are not of type
basic_simd < T , Abi > is wellformed.
basic_simd < T , Abi > Each function overload produced by the above rules applies the indicated
function elementwise. For the mathematical functions, the results per element only need to be approximately equal to the application of the function which is overloaded for the element type.
< complex > The result is unspecified if a domain, pole, or range error occurs when the input argument(s) are applied to the indicated
function. [ Note: Implementations are encouraged to follow the C specification (especially Annex F). — end note ]
< complex >
6. Polls
6.1. Kona 2022 SG1 polls
Poll: After significant experience with the TS, we recommend that the next version (the TS version with
improvements) of
target the IS (C++26)
SF  F  N  A  SA 

10  8  0  0  0 
Poll: Future papers and future revisions of existing papers that target
should go directly to LEWG. (We do not believe
there are SG1 issues with
today.)
SF  F  N  A  SA 

9  8  0  0  0 
7. Revision History
R3 => R4

Updated wording to reflect changes made to
in [P1928R6] (e.g., removal ofstd :: simd
, addition offixed_size_simd
).basic_simd
R2 => R3

Added wording
R1 => R2

Add more content for proposal overview

Rewrite to bikeshed
R0 => R1

Add polls from Kona SG1 2022