I have a program that maps device registers to unions of bitfields and integers. This is part of a larger code base, so although this is very C-style code, we are compiling it as C++20. Here is a contrived example that matches what we had.
#include <stdint.h>
typedef union {
uint16_t reg;
struct {
uint16_t x : 4;
uint16_t y : 4;
uint16_t z : 4;
};
} PositionReg;
typedef union {
uint16_t reg;
struct {
uint16_t yaw : 4;
uint16_t pitch : 4;
uint16_t roll : 4;
};
} AttitudeReg;
typedef struct {
PositionReg xyz;
AttitudeReg ypr;
} Frame;
uint16_t get_position() {
return 0xDEAD;
}
uint16_t get_attitude() {
return 0xBEEF;
}
// This compiles
Frame get_frame() {
Frame f = {
.xyz = get_position(),
.ypr = get_attitude(),
};
return f;
}
// This won't compile
Frame get_frame_broken() {
PositionReg xyz = get_position();
AttitudeReg ypr = get_attitude();
Frame f;
f.xyz = xyz;
f.ypr = ypr;
return f;
}
What I don't understand is why it broke when I added the function get_frame_broken
. The compiler had no issue implicitly converting uint16_t
to PositionReg
inside the designated initializer, but when I try to do that outside the designated initializer it fails to compile with the following error (Godbolt link)
<source>: In function 'Frame get_frame_broken()':
<source>:46:35: error: conversion from 'uint16_t' {aka 'short unsigned int'} to non-scalar type 'PositionReg' requested
46 | PositionReg xyz = get_position();
| ~~~~~~~~~~~~^~
<source>:47:35: error: conversion from 'uint16_t' {aka 'short unsigned int'} to non-scalar type 'AttitudeReg' requested
47 | AttitudeReg ypr = get_attitude();
| ~~~~~~~~~~~~^~
Compiler returned: 1
Why does this cause an error?
Also, is there any clean or simple way to force the implicit conversion? Or am I stuck with splitting it into two lines like this:
// This compiles
Frame get_frame_broken() {
// PositionReg xyz = get_position();
// AttitudeReg ypr = get_attitude();
PositionReg xyz;
AttitudeReg ypr;
xyz.reg = get_position();
ypr.reg = get_attitude();
Frame f;
f.xyz = xyz;
f.ypr = ypr;
return f;
}
If you switch to clang, you get this warning:
warning: brace elision for designated initializer is a C99 extension [-Wc99-designator]
So, the fact that you can omit the braces is an extension. In general, just put the braces in:
Frame get_frame_broken() {
PositionReg xyz{get_position()};
AttitudeReg ypr{get_attitude()};
Frame f;
f.xyz = xyz;
f.ypr = ypr;
return f;
}