Search code examples
c++attributesswitch-statementc++20branch-prediction

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


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.


Solution

  • 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();.