Ok, I will elaborate.
1. Operations on imaginary part.
//example with imaginary
#include <complex.h>
#ifndef _Imaginary_I
# error "No imaginary support"
#endif
double imaginary a, b;
double imaginary c = a + b;
//example without imaginary
double a, b;
double c = a + b;
Note, example without imaginary is not less readable, but it’s easier on the compiler. There is no point in requiring imaginary types, for calculations on them.
2. Converting to complex
#include <complex.h>
#ifndef CMPLX //possible before C11
# ifdef _Imaginary_I
# define CMPLX(x, y) ((double complex)( \
(double)(x) + (double)(y) * _Imaginary_I ))
# else
# define CMPLX(x, y) (*(double complex*)(double[2]){x,y})
# endif
#endif
Note, if defined like that, it can’t be used were constant expression is required (cause of 2nd variant). This includes static initializer, but why would anyone need negative zero in static?
3. Standard violation
No, there is no violation. Annex G is fully optional. Compiler not implementing Annex G can be fully standard complaint, if it doesn’t pre-define __STDC_IEC_559_COMPLEX__ (since C99, until C23), or __STDC_IEC_60559_COMPLEX__ (since C23).
Now, according to cppreference, before C11 some compilers did pre-define __STDC_IEC_559_COMPLEX__ without implementing imaginary types. So, “POSIX recommends checking if the macro _Imaginary_I is defined […]”. I don’t know why those compilers did this, but it won’t tell us why more compilers didn’t implement imaginary types within 24 years.
Btw, also from cppreference:
To date, only Oracle C compiler is known to have implemented imaginary types.
Why would compilers not support this optional feature?
First, I need to say, I have an assumption that it’s not a choice out of negligence. I think it’s deliberate choice, for simplicity and optimization. My reasoning is that, they are implementing other less documented extensions. For example, (because of current PEP 505 discussion) the oldest coalescing operator I know about: the binary ternary operator. (For pointers a and b,
expression a?:b will return a only if it’s not null.)
It needs to be understood, that C creation process is fundamentally different from (for example) Python’s. First lets look at standard creation:
- ISO WG14 drafts update to standard.
- Some compiler maintainers will see those and implement them, or comment why those changes are bad idea.
- ISO updates the standard.
One might think the language will only grow, but no it won’t. Note there is no reference implementation in this process. Compilers don’t need to mimic one all-including compiler. If some feature makes the compiler worse, it won’t be implemented.
Here is my reasoning:.
- Since support for imaginary types is poor, old large projects would support compilers without it. Therefore there is no production code that won’t compile without it.
- Since support for imaginary types is poor, but they can easily be replaced by floating point types, aka the most benchmarked types in existence. In order to be faster, good code would use floating point types instead. Even if not on this compiler, on some other compiler it would make a difference.
- Is it worth it, to allow maybe unoptimized or poorly maintained part of the compiler, to support new bad code?
I’d like to point out that’s my view, actual reason could be different. I also don’t know the details on ISO/IEC 60559.