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)?

Drobot Viktor
  • 323
  • 2
  • 11
  • 1
    "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. – Eugene Oct 15 '21 at 23:18
  • Pretty sure if it is valid going forward, it is also valid going backwards. – NathanOliver Oct 15 '21 at 23:26
  • 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. – StoryTeller - Unslander Monica Oct 15 '21 at 23:44
  • _Is it true for the backward reinterpreting?_ No, using `cppComplexArray` in pointer arithmetic will violate [\[expr.add\]/6](https://timsong-cpp.github.io/cppwp/n4868/expr.add#6.sentence-1) – Language Lawyer Oct 16 '21 at 09:29
  • _C++11 standard says this_ I've opened C++11 draft https://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. – Language Lawyer Oct 17 '21 at 09:39

2 Answers2

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.

Christophe
  • 68,716
  • 7
  • 72
  • 138
  • 1
    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. – Drobot Viktor Oct 16 '21 at 07:07
  • btw, if I will create "double representation" of complex array, e. g. `double *dblArr = reinterpret_cast(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)? – Drobot Viktor Oct 16 '21 at 07:16
  • 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-defined – Drobot Viktor Oct 16 '21 at 08:04
  • 1
    @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. – Christophe Oct 16 '21 at 09:45
  • @DrobotViktor _if I will create "double representation" of complex array, e. g. `double *dblArr = reinterpret_cast(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. – Language Lawyer Oct 16 '21 at 11:26
  • @DrobotViktor _if I will create "double representation" of complex array, e. g. `double *dblArr = reinterpret_cast(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(cmplxArr)[i]`, `double *dblArr = reinterpret_cast(cmplxArr); dblArr[i];` is not guaranteed to have the same well-defined behavior. – Language Lawyer Oct 16 '21 at 11:56
  • 1
    @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. – BitTickler Oct 16 '21 at 14:48
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)

Language Lawyer
  • 3,378
  • 1
  • 12
  • 29
  • 1
    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](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2020/p1912r1.pdf) section 4) – Christophe Oct 16 '21 at 14:48
  • 1
    @Christophe the usual [expr.add] rules are ofc. sidestepped, but only for expressions of the forms `reinterpret_cast(p)[2*i]` and `reinterpret_cast(p)[2*i + 1]`, where `p` is a pointer to an array element of type `std::complex` and `i` is an expression of an integer type. – Language Lawyer Oct 16 '21 at 14:55
  • 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 ? – Christophe Oct 16 '21 at 15:31
  • @Christophe _DOesn't the general rule for reinterpreting pointers allow to use the obtained pointer value in other expressions ?_ Use `reinterpret_cast(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? – Language Lawyer Oct 16 '21 at 15:36
  • 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 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 changed – Drobot Viktor Oct 17 '21 at 07:21
  • @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. – Language Lawyer Oct 17 '21 at 09:37
  • so, the only fully portable way is to go through the array of `double`s and convert them element-wise to the `std::complex`< right? – Drobot Viktor Oct 17 '21 at 16:58