Erik Rigtorp

Uses of immediately invoked function expressions (IIFE) in C++

The immediately invoked function expression (IIFE) is a concept that has been independently discovered multiple times and applicable to multiple programming languages. In C++ a IIFE is a lambda expression that is immediately invoked as soon as it is defined:

[]{ ... }();

It’s particularly useful for converting a series of statements into an expression.

Complex initialization

To initialize a class member variable using some complex logic we can use a IIFE:

struct Foo {
    int baz;
    Foo(int bar) : baz([&]{
        // Complex initialization of baz
    }()) {}
}

This avoid creating a separate static member function. With delegating constructors there’s seldom any need to use a static member function to prevent duplication of the initialization code.

To initialize a const variable using some complex logic we can also use a IIFE:

const int foo = [&]{
    // Complex initialization of foo
}();

Even if you are initializing a non-const variable a IIFE can be useful to limit the scope of scratch variables needed for the initialization.

The alternatives to IIFE is using the ternary operator (?) or the non-standard GCC extension statement expressions.

The C++ Core Guidelines recommends using IIFE for complex initialization.

Jason Turner has a great video looking into how IIFE is optimized.

Forcing code out of line

This is a trick I learned from Matt Godbolt.

When using exceptions for error handling, GCC will assume that the error branch is unlikely to occur and put the error handling code into a separate cold section out of line from the normal code. This reduces pressure on the instruction cache and can increase the performance of your code.

If you are using return values for error handling:

const bool failed = foo();
if (failed) {
  printf("error!");
  return;
}

GCC will not automatically put the error handling code out of line in the cold section. We can use the C++20 attribute [[unlikely]] to hint the compiler to at least optimize the code layout:

const bool failed = foo();
if (failed) [[unlikely]] {
  printf("error!");
  return;
}

With IIFE we can do even better by using the GCC specific function attributes noinline1 and cold2:

const bool failed = foo();
if (failed) {
  [&]() __attribute__((noinline,cold)) {
    printf("error!");
  }();
  return;
}

GCC will now place the error handling call out of line in the cold section and mark the branch as unlikely2.

Compare the difference in code generation on Compiler Explorer.

References


  1. “This function attribute prevents a function from being considered for inlining.” https://gcc.gnu.org/onlinedocs/gcc/Common-Function-Attributes.html#Common-Function-Attributes ↩︎

  2. “The cold attribute on functions is used to inform the compiler that the function is unlikely to be executed. The function is optimized for size rather than speed and on many targets it is placed into a special subsection of the text section so all cold functions appear close together, improving code locality of non-cold parts of program. The paths leading to calls of cold functions within code are marked as unlikely by the branch prediction mechanism. It is thus useful to mark functions used to handle unlikely conditions, such as perror, as cold to improve optimization of hot functions that do call marked functions in rare occasions.

    When profile feedback is available, via -fprofile-use, cold functions are automatically detected and this attribute is ignored.” https://gcc.gnu.org/onlinedocs/gcc/Common-Function-Attributes.html#Common-Function-Attributes ↩︎