typedef struct {
_Bool ok;
union {
double value;
int error;
};
} Result_double;
Result_double x = strtod·or·error(s, NULL);
if (!x.ok) {
handle(x.error);
return;
}
use(x.value); typedef struct {
double value;
int error;
} maybe_double;
Is most efficient on x86-64 SYSV, because the ABI supports the `error` being returned in a GP register, and the `value` in a vector register. The struct is returned in `rax:xmm0` - the double is where it is meant to be.When you use a union containing an int and a double, the double has to be moved to a GP register, and back to the vector register when read as a double (via movq), so there's a small hidden cost compared to the former approach. Your `Result_double` is returned in `rax:rdx`.
There's a trick you can use to avoid this - keep the `double` in the vector register and move the `int` to the vector register instead - so you only pay the cost to move between GP and vector registers when there's an error but not otherwise. To do this, make the union use `__m64` instead of `int` - the whole union is then in a vector register, and the `Result_double` struct is returned in `rax:xmm0`, as with `maybe_double` struct.
typedef struct {
_Bool ok;
union {
double value;
__m64 error;
};
} Result_double;
Of course, this is x86-64 SYSV only. On AARCH64, the double will always be moved to a GP register in either example. Other conventions may also differ - MSVC in particular is poor because it converts to pass-by-pointer to a stack allocated structure for any struct greater than 64-bits.On MSVC we could maybe get maximum efficiency by using NaN-boxing - store the error code in the signalling NaN space - eg, treat NaNs beginning 0x7FF1 as error codes.
typedef union {
int64_t _as_int;
double _as_double;
} Result_double;
#define Result_double_value(n) \
(Result_double){ ._as_double = (n) };
#define Result_double_error(errcode) \
(Result_double){ ._as_int = (0x7FF1000000000000ULL | (errcode)) }
#define Result_double_is_error(x) \
(((x)._as_int & 0x7FFF000000000000ULL) == 0x7FF1000000000000ULL)
#define Result_double_get_error(x) \
((x)._as_int & 0x0000FFFFFFFFFFFFULL)
#define Result_double_get_value(x) \
((x)._as_double)
Result_double x = strtod·or·error(s, NULL)
if (Result_double_is_error(x)) {
handle(Result_double_get_error(x));
return;
}
use(Result_double_get_value(x))
The `Result_double` then gets returned in `rax` and avoids touching the stack. Could probably improve this to return in `xmm0` instead.