Skip to content

CWG3134 [expr.cond] Unclear behavior of bit-fields in conditional operators #820

@Halalaluyafail3

Description

@Halalaluyafail3

Full name of submitter (unless configured in github; will be published with the issue): Jay Ghiron

Reference (section label): [expr.cond]/5

If the second and third operands are glvalues of the same value category and have the same type, the result is of that type and value category and it is a bit-field if the second or the third operand is a bit-field, or if both are bit-fields.

Issue description:

Currently, if the second and third operands of a conditional operator have the same type and value category then the resulting expression has the same type and value category as both the second and third operands. Additionally, if both the second and third operands are glvalues and either is a bit-field then the resulting expression is also a bit-field. This wording creates two confusing situations. First, if both the second and third operands are bit-field glvalues of the same value category and type but do not have the same widths. The wording requires the resulting expression to be a bit-field glvalue, but does not specify what the width is. Secondly, if either the second or third operand is a bit-field glvalue and the other is a non-bit-field glvalue of the same value category and type. The wording requires the resulting expression to be a bit-field glvalue, even though it might not end up referring to a bit-field object.

#include<limits>
static_assert(std::numeric_limits<int>::max()<std::numeric_limits<long long>::max());
int main(){
    struct{int x:3,y:4;}a;
    struct{unsigned x:std::numeric_limits<unsigned>::digits-1,y:std::numeric_limits<unsigned>::digits;}b{};
    int c;
    //a.y can represent 7, a.x cannot represent 7
    (0?a.x:a.y)=7;//does this surely store 7 in a.y?
    (1?a.x:a.y)=7;//if the answer to the above is yes, does this store an implementation-defined value in a.x or is a specific value guaranteed?
    auto d=(1?b.x:b.y)-1;
    d<0;//is this expression true or false? that is, does promoting (1?b.x:b.y) result in int or unsigned
    (1?c:a.x)=7;//does this surely store 7 in c?
    (0?c:a.x)=7;//if the answer to the above is yes, does this store an implementation-defined value in a.x or is a specific value guaranteed?
    (1?c:a.x)=std::numeric_limits<int>::max()+1LL;//if 7 was surely stored in c before, does this surely store std::numeric_limits<int>::min() in c or is an implementation-defined value stored?
    //the expressions above which assigned to c did so with bit-field expressions, what memory location was modified by those assignments?
    unsigned e{};
    auto f=(1?b.x:e)-1;
    f<0;//is this expression true? that is, does promoting (1?b.x:e) result in int or unsigned
}

GCC, Clang, and MSVC all agree on this behavior:
(0?a.x:a.y)=7 does store 7
(1?a.x:a.y)=7 stores -1 (implementation-defined value)
d<0 is false, it promotes to unsigned
(1?c:a.x)=7 does store 7
(0?c:a.x)=7 stores -1 (implementation-defined value)
(1?c:a.x)=std::numeric_limits<int>::max()+1LL does store std::numeric_limits<int>::min()
f<0 is false, it promotes to unsigned

The first 4 assignments I think can have their behavior reasonably intuited from the current wording (using the width of the selected branch), even if it is not very clear. The last assignment appears to assign an implementation-defined value from the current wording, though I cannot imagine that is the intent and no implementations appear to assign a different value than would occur with wraparound. There is no clear wording to judge whether or not d<0 and f<0 should be true or false. I have not tested how any implementation views the assignments to c in terms of which memory locations are modified.

GCC, Clang, and MSVC also disagree with the intuitive interpretation of what should happen with a conditional operator where the second and third operands are both bit-field glvalues of the same value category, type, and width.

int main(){
    struct{unsigned u:1;}u{};
    auto y=(1?u.u:u.u)-1;
    y<0;//is this expression true? that is, does promoting (1?u.u:u.u) result in int or unsigned
}

All three implementations agree that y<0 is false, that is (1?u.u:u.u) promotes to unsigned. Both GCC and Clang agree that with u.u instead of (1?u.u:u.u), the promoted type should be int instead. MSVC also has u.u promote to unsigned, but that is surely a bug. So it seems like even if the widths match in a conditional operator, implementations still intentionally disregard that when determining the promoted type. GCC and Clang diverge about how to promote bit-fields inside of conditional operators.

int main(){
    struct{unsigned u:1;}u{};
    auto y=(1?u.u:0)-1;
    y<0;//true with Clang, false with GCC and MSVC
}

This also seems like a bug, the language is clear here that the usual arithmetic conversions should take place.

All three implementations also have issues implementing conditional operators that result in bit-fields when the resulting expression is assigned to, even when both operands are bit-fields and their widths match.

int main(){
    struct{int x:4;}a;
    struct{int y:2,x:4;}b;
    int r=1;
    (r?a.x=0:b.x=0)+=1;
}

This is rejected by all three implementations, for different reasons. GCC has issues ensuring the correct evaluation order in these cases, see https://gcc.gnu.org/bugzilla/show_bug.cgi?id=122772. Clang just says "cannot compile this conditional operator yet". MSVC does not recognize (r?a.x=0:b.x=0) as an lvalue, and this even changes the meaning of declarations such as decltype(r?a.x=0:b.x=0)q; which MSVC accepts (surely a bug). MSVC seems to be able to correctly handle this program if either the assignment to a.x or b.x is removed, or both.

Considering the implementation difficulty, it might be worth drastically changing the semantics of conditional operators that currently result in bit-fields. No proposed wording is provided, but here are some possible resolutions (from most to least severe):

  • Reject all conditional expressions that currently are specified as resulting in bit-fields, or change them to prvalues instead.
  • Reject conditional expressions that mix different widths, bit-fields and non-bit-fields, or bit-fields of different classes; or change them to prvalues instead. Clarify that the width of the bit-field from a conditional operator is the same as its operands.
  • Reject conditional expressions that mix different widths or bit-fields and non-bit-fields, or change them to prvalues instead. Clarify that the width of the bit-field from a conditional operator is the same as its operands.
  • Reject conditional expressions that mix bit-fields and non-bit-fields, or change them to prvalues instead. Clarify the widths of bit-fields from conditional operators, including when the widths are different.
  • No changes to value categories or what expressions are allowed. Clarify the widths of bit-fields from conditional operators, including when the widths are different or bit-fields and non-bit-fields are mixed. Clarify what it means to have a bit-field expression refer to a non-bit-field object.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions