2

I am aware of the strong type-aliasing rule. However, cppreference notes that

An implementation cannot declare additional non-static data members that would occupy storage disjoint from the real and imaginary components and must ensure that the class template specialization does not contain any padding. The implementation must also ensure that optimizations to array access account for the possibility that a pointer to value_type may be aliasing a std::complex specialization or array thereof.

Does this requirement allow code like the following to be legal, even if it isn't necessarily moral?

std::size_t numDoubles = 100;
double* a = (double*) std::malloc(numDoubles * sizeof(double));
std::complex<double>* b = reinterpret_cast<std::complex<double>*>(a);
DoSomethingWith(b[1]);

Does the answer change if new[] is used to generate the array-of-double?

XY Problem Explanation: I'm trying to abstract over an allocator provided by a third-party library that might not be present; that allocator returns a void*; I'm trying to avoid the does-the-library-exist details leaking into the header. So I have a structure like the following:

// foo.h
namespace impl {
    void* Allocate(std::size_t numBytes);
}

template<typename T>
T* Allocate(std::size_t num) {
    static_assert(std::is_pod_v<T>);
    return static_cast<T*>(Allocate(num * sizeof(T)));
}


// foo.cpp
#if LIBRARY_PRESENT

void* impl::Allocate(std::size_t numBytes) { return LibraryAllocate(numBytes); }

#else

void* impl::Allocate(std::size_t numBytes) { return std::malloc(numBytes); }

#endif

Unfortunately, std::complex is not POD, because it has a non-trivial default constructor. I'm hoping I can ignore that problem.

James Picone
  • 1,509
  • 9
  • 18

2 Answers2

1

Does this requirement allow code like the following to be legal

That code isn't strictly legal even if you used a directly instead of doing the cast to complex. The C++ object model does not allow you to just grab some memory, cast it to a type, and pretend that objects exist in that memory. This is true regardless of the type (outside of bytewise types).

That code never creates an array of double, so you cannot access the memory as though an array of double were there. Nor can you access a complex<double> as though it were there.

Does the answer change if new[] is used to generate the array-of-double?

Only in the sense that there will be a legitimate array of double. There still isn't a complex<double> there.

The rule that std::complex<T> provides is that you can access its members as though it were an array of two Ts. This does not mean you can access any array of two Ts as though it were a std::complex<T>.

Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982
  • Fair enough. Looks like I'll have to placement-new in the malloc'd memory I guess? – James Picone Oct 18 '18 at 05:13
  • @JamesPicone: Well, that's the question: are you allocating memory or are you creating objects? The C++ standard library allocator model has separate functions for these separate tasks: `allocator::allocate` allocates memory appropriate for `T`, while `allocator::create` creates objects in such storage. – Nicol Bolas Oct 18 '18 at 05:15
  • Not a lot you can do with allocated memory other than creating objects in it. To try and explain the abstraction, I'm trying to get an array of some type T, typically either double or std::complex, but the memory that I'm using for the array may be coming from CUDA allocation function that puts it somewhere special and returns a void*. And because I'm trying to do this in a .cpp file I don't have type information, making std::allocator::allocate or moral equivalents not really functional. – James Picone Oct 18 '18 at 05:29
1

So the standard section [complex.numbers]p4 says the following:

If z is an lvalue of type cv complex then:

  • the expression reinterpret_­cast(z) shall be well-formed,
  • reinterpret_­cast(z)[0] shall designate the real part of z, and
  • reinterpret_­cast(z)[1] shall designate the imaginary part of z.

Moreover, if a is an expression of type cv complex* and the expression a[i] is well-defined for an integer expression i, then:

  • reinterpret_­cast(a)[2*i] shall designate the real part of a[i], and
  • reinterpret_­cast(a)[2*i + 1] shall designate the imaginary part of a[i].

It does not carve out an exception to the strict aliasing rule the other way around so it looks like it is not valid to alias std::complex<double> via a double.

Using new does not change the analysis.

Shafik Yaghmour
  • 154,301
  • 39
  • 440
  • 740