reinterpreting array of doubles as array of std::complex<double>
Asked Answered
L

2

8

While C++11 standard says this about reinterpreting std::complex<double> as doubles:

For any pointer to an element of an array of complex<T> named p and any valid array index i, reinterpret_cast<T*>(p)[2*i] is the real part of the complex number p[i], and reinterpret_cast<T*>(p)[2*i + 1] is the imaginary part of the complex number p[i]

The intent of this requirement is to preserve binary compatibility between the C++ library complex number types and the C language complex number types (and arrays thereof), which have an identical object representation requirement.

Is it true for the backward reinterpreting? I mean is it safe to perform something like this: std::complex<double> *cppComplexArray = reinterpret_cast<std::complex<double> *>(cDoublesArray) where cDoublesArray have a type of double * and even length 2 * n? What are potential pitfalls if its length will be odd (2 * n + 1)?

Lated answered 15/10, 2021 at 22:56 Comment(5)
"Obviously", the backward reinterpreting should work, too - if you have an even number of elements in an array of double. But are you looking for proof of it using some wording from C++ standard? If so, please add "language-lawyer" tag.Dialyze
Pretty sure if it is valid going forward, it is also valid going backwards.Maurist
If std::complex itself is a well-behaved enough type... maybe. Otherwise C++ has some dark corners in its object model (that vary as the years go by). It may be ill-formed C++17 and valid C++20. I'd hate to try and prove it formally though.Bragi
Is it true for the backward reinterpreting? No, using cppComplexArray in pointer arithmetic will violate [expr.add]/6Remittance
C++11 standard says this I've opened C++11 draft timsong-cpp.github.io/cppwp/n3337/complex.numbers and couldn't find the word «intent» there. If you're quoting cppreference, don't say that it is the Standard, since it is not.Remittance
R
1

Is it true for the backward reinterpreting? I mean is it safe to perform something like this: std::complex<double> *cppComplexArray = reinterpret_cast<std::complex<double> *>(cDoublesArray)

Casting/initialization itself is safe, using the result as-if pointing to an element of an array of std::complex<double> is not.

When cDoublesArray (or the array-to-pointer conversion applied to it, if cDoublesArray denotes an array of doubles) points to the first element of an array of doubles, reinterpret_cast<std::complex<double>*>(cDoublesArray) does the same (has the same value).

Using an expression of type std::complex<double>* whose value «pointer to an object of type double» (like reinterpret_cast<std::complex<double>*>(cDoublesArray) or cppComplexArray) in pointer arithmetic (e.g. cppComplexArray + 0) would violate [expr.add]/6:

For addition or subtraction, if the expressions P or Q have type “pointer to cv T”, where T and the array element type are not similar, the behavior is undefined.

(T is std::complex<double>, array element type is double here, and they are not similar)

Remittance answered 16/10, 2021 at 14:33 Comment(7)
Interesting. The general rule for pointer arithmetic are indeed quite strict (i.e. pointers of the same type and in the range of a same array). However, isn't this rule not sidestepped by the requirement of the valid reinterpret cast and the intent to "preserve binary compatibility between the C++ library complex number types and the C language complex number types (and arrays thereof)" ? (see for example this analysis section 4)Braynard
@Braynard the usual [expr.add] rules are ofc. sidestepped, but only for expressions of the forms reinterpret_cast<T*>(p)[2*i] and reinterpret_cast<T*>(p)[2*i + 1], where p is a pointer to an array element of type std::complex<T> and i is an expression of an integer type.Remittance
DOesn't the general rule for reinterpreting pointers allow to use the obtained pointer value in other expressions ? And wouldn't an exception to these principles in the specific case of complex lead to inconsistencies ?Braynard
@Braynard DOesn't the general rule for reinterpreting pointers allow to use the obtained pointer value in other expressions ? Use reinterpret_cast<T*>(p) whatever you like, except that it could have UB in some cases. And wouldn't an exception to these principles in the specific case of complex lead to inconsistencies ? Inconsistencies with what?Remittance
ok, seems like using direct backward reinterpretation is not a portable and well-defined way. but what if to operate directly on reinterpreted as double * source array std::complex<double> x* (see my comment to the Christophe's answer)? Seems like standard says that direct operation should be fine because of the intent to support the same object representation of complex datatype both in c and c++, and in C starting from c99 (I use c11) complex values are stored as two adjacent sub-values. I've tried with modifying double * array from C function and original C++ complex data has changedLated
@DrobotViktor I think I've already answered that the standard overrides the behavior of special forms of expressions. But not for constructs which don't have this form.Remittance
so, the only fully portable way is to go through the array of doubles and convert them element-wise to the std::complex<double>< right?Lated
B
1

In practice the backward reinterpreting will probably work most of the time, in view of the strong constraints of the forward reinterpreting impose (see on cppreference, std::complex, implementation notes).

However, I'm not totally sure that such backward reinterpreting would in always work in theory:

  • Let's imagine an absurd and hypothetical implementation of a complex library that would maintain a list of addresses of active complex objects (e.g. for debugging purpose). This (probably static) list would be maintained by the complex constructor and destructor.
  • Every complex operation in this library would verify if its operands are in the list.
  • While forward reinterpreting would work (the complex objects were well constructed and its parts can be used as doubles), the backward reinterpretation would not work (e.g. despite a compatible layout, you would reinterpret as complex a pair of doubles and if you'd perform any complex operation on them, it would fail since the complex was not properly constructed, i.e. its address is not in the list).

As said, this complex library would probably be a stupid idea. But such a library could be implemented and compliant with the standard specs. This is sufficient to prove that there is no reverse guarantee in theory.

The last point of your question is easier and more obvious to answer, supposing we would have an implementation where the reverse reinterpretation works. The missing last column would lead to an access of a part that is out of bounds. This would therfore lead to UB.

Additional readings: This working paper of the standard committee requests a general convertability feature that would generalize the bahavior of reinterpret_cast on complex for other types. It explains the complex case in section 4 and the special handling required from the compiler to make it work if complex is not itself implemented by an array.

Braynard answered 16/10, 2021 at 0:6 Comment(7)
ok, I think, it's better to stay with element-by-element copying of the given doubles to the manually created array of complex values.Lated
btw, if I will create "double representation" of complex array, e. g. double *dblArr = reinterpret_cast<double *>(cmplxArr) and then pass this pointer to the external function, for example, C function which will store result in it - will be the data in the original complex storage modified? I mean, if original cmplxArr contained 3 complex double values (1.0+1.0i, -2.5+4.0i, 1.2-0.1i) and C function just multiplies given dblArr by 2, will the contents of cmplxArr be like that (2.0+2.0i, -5.0+8.0i, 2.4-0.2i)?Lated
just tested and it worked fine, however, I'm not sure if it is portable and well-defined way to do so. UPD: re-read quote from standard and it seems like for any valid indices and pointers behavior should be well-definedLated
@DrobotViktor yes, if you pass the pointer to the double array and your fonction modifies the content of the pointed object, the original data will be modified. And this is ok : it's a consequence of the reinterpreting guarantee, so you can fully rely on this.Braynard
@DrobotViktor if I will create "double representation" of complex array, e. g. double *dblArr = reinterpret_cast<double *>(cmplxArr) and then pass this pointer to the external function, for example, C function which will store result in it - will be the data in the original complex storage modified? Strictly speaking, C objects ≠ C++ objects, C pointers ≠ C++ pointers etc. The languages have each own core language wording, which doesn't translate to the other's one. So, basically, your question is unanswerable.Remittance
@DrobotViktor if I will create "double representation" of complex array, e. g. double *dblArr = reinterpret_cast<double *>(cmplxArr) and then pass this pointer to the external function, for example, C function which will store result in it - will be the data in the original complex storage modified? The standard specifies the behavior of reinterpret_cast<double *>(cmplxArr)[i], double *dblArr = reinterpret_cast<double *>(cmplxArr); dblArr[i]; is not guaranteed to have the same well-defined behavior.Remittance
@LanguageLawyer *Strictly speaking, C objects ≠ C++ objects, C pointers ≠ C++ pointers etc. * Mixing of C and C++ code is and always will be a feature of the C/C++ universe. For functions, there is extern "C" and typically, C-functions which take struct arguments also define the struct. If C++ code now instantiates such a struct and passes a pointer to the C function as an argument, this is also 100% okay. Your wording does not reflect that.Pedestrianism
R
1

Is it true for the backward reinterpreting? I mean is it safe to perform something like this: std::complex<double> *cppComplexArray = reinterpret_cast<std::complex<double> *>(cDoublesArray)

Casting/initialization itself is safe, using the result as-if pointing to an element of an array of std::complex<double> is not.

When cDoublesArray (or the array-to-pointer conversion applied to it, if cDoublesArray denotes an array of doubles) points to the first element of an array of doubles, reinterpret_cast<std::complex<double>*>(cDoublesArray) does the same (has the same value).

Using an expression of type std::complex<double>* whose value «pointer to an object of type double» (like reinterpret_cast<std::complex<double>*>(cDoublesArray) or cppComplexArray) in pointer arithmetic (e.g. cppComplexArray + 0) would violate [expr.add]/6:

For addition or subtraction, if the expressions P or Q have type “pointer to cv T”, where T and the array element type are not similar, the behavior is undefined.

(T is std::complex<double>, array element type is double here, and they are not similar)

Remittance answered 16/10, 2021 at 14:33 Comment(7)
Interesting. The general rule for pointer arithmetic are indeed quite strict (i.e. pointers of the same type and in the range of a same array). However, isn't this rule not sidestepped by the requirement of the valid reinterpret cast and the intent to "preserve binary compatibility between the C++ library complex number types and the C language complex number types (and arrays thereof)" ? (see for example this analysis section 4)Braynard
@Braynard the usual [expr.add] rules are ofc. sidestepped, but only for expressions of the forms reinterpret_cast<T*>(p)[2*i] and reinterpret_cast<T*>(p)[2*i + 1], where p is a pointer to an array element of type std::complex<T> and i is an expression of an integer type.Remittance
DOesn't the general rule for reinterpreting pointers allow to use the obtained pointer value in other expressions ? And wouldn't an exception to these principles in the specific case of complex lead to inconsistencies ?Braynard
@Braynard DOesn't the general rule for reinterpreting pointers allow to use the obtained pointer value in other expressions ? Use reinterpret_cast<T*>(p) whatever you like, except that it could have UB in some cases. And wouldn't an exception to these principles in the specific case of complex lead to inconsistencies ? Inconsistencies with what?Remittance
ok, seems like using direct backward reinterpretation is not a portable and well-defined way. but what if to operate directly on reinterpreted as double * source array std::complex<double> x* (see my comment to the Christophe's answer)? Seems like standard says that direct operation should be fine because of the intent to support the same object representation of complex datatype both in c and c++, and in C starting from c99 (I use c11) complex values are stored as two adjacent sub-values. I've tried with modifying double * array from C function and original C++ complex data has changedLated
@DrobotViktor I think I've already answered that the standard overrides the behavior of special forms of expressions. But not for constructs which don't have this form.Remittance
so, the only fully portable way is to go through the array of doubles and convert them element-wise to the std::complex<double>< right?Lated

© 2022 - 2025 — McMap. All rights reserved.