c++implicit-conversionunions

Implicit conversion from int to union works in designated initializer but otherwise fails to compile


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;
}

Solution

  • 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;
    }