Correctly using C++20 `[[likely]]`/`[[unlikely]]` in `switch` statements
Asked Answered
A

1

9

C++20 has handy [[likely]]/[[unlikely]] attributes which guide code generation. For example, you can specify a branch is likely to be taken by:

if (b) [[likely]] { /*...*/ }

Similarly, it is possible to use these attributes in switch statements . . . somehow? The documentation suggests the following example (slightly formatted):

switch (i) {
    case 1:
        [[fallthrough]];
    [[likely]] case 2:
        return 1;
}

The implication is clearly that the [[likely]]/[[unlikely]] goes before the case statement. The internet seems to almost universally promulgate this usage.

However, consider the following similar code (all I've done is move the [[likely]] to the other case):

switch (i) {
    [[likely]] case 1:
        [[fallthrough]];
    case 2:
        return 1;
}

This fails to compile on clang! While that may be related to a compiler bug with [[fallthrough]], it got me looking at the standards. The relevant standard has the following example (see §VII):

implementations are encouraged to optimize for that case being executed (eg. a having the value 1 in the following code):

switch (a) {
    case 1: [[likely]]
        foo();
        break;
    //...
}

That is, the attribute comes after the case label, not before.

So . . . which is it? Offhand, I'd expect the standard to be correct, but that is actually a proposal, not the real standard AFAICT—it could have been changed since. And, I'd expect the documentation, if nothing else, to be correct at least about the fundamental syntax—except that it doesn't even compile.

Absorbent answered 5/3, 2021 at 16:29 Comment(0)
B
5

Both examples are valid, and Clang is exhibiting a bug. The relevant verbiage from the last standard draft for C++20 is

[dcl.attr.likelihood]

1 The attribute-tokens likely and unlikely may be applied to labels or statements.

Where the relevant grammar productions for statements and labeled statements have an attribute specifier sequence at the appropriate locations.

[stmt.pre]

statement:
  labeled-statement
  attribute-specifier-seq expression-statement
  attribute-specifier-seq compound-statement
  attribute-specifier-seq selection-statement
  attribute-specifier-seq iteration-statement
  attribute-specifier-seq jump-statement
  declaration-statement
  attribute-specifier-seq try-block

[stmt.label]

labeled-statement:
  attribute-specifier-seq identifier : statement
  attribute-specifier-seq case constant-expression : statement
  attribute-specifier-seq default : statement

In

switch (i) {
    case 1:
        [[fallthrough]];
    [[likely]] case 2:
        return 1;
}

The attribute applies to case 2: wheras in

switch (a) {
    case 1: [[likely]]
        foo();
        break;
    //...
}

it applies to the statement foo();.

Berkow answered 5/3, 2021 at 16:42 Comment(2)
While brevity is nice, I feel like the language grammar rules are highly relevant here. Also, I'm having a hard time seeing a difference between [[likely]] applying to foo(); vs. to the case statement, since (outside of fallthrough or goto) the case statement is the only way foo(); could be reached. Sure, I can see [[likely]] foo(); working as an isolated construct, but the paper did explicitly say this affects the case . . .Absorbent
@imallett - It said it should affect the case (not goto labels however), but the normative wording proposed there only mentions statements. Nor does the proposal touch upon the required grammar changes. The standard contains more refined wording. And you see no difference because there is none. The normative wording is simply designed to allow more flexible placement.Berkow

© 2022 - 2024 — McMap. All rights reserved.