yuqi-zheng

Immediately Invoked Function Expressions (IIFE) in C++


An Immediately Invoked Function Expression (IIFE) is a lambda that is defined and called at the same point in the source code. In C++ the syntax is:

[]{...}();

The construct turns a sequence of statements into a single expression, which opens up two genuinely useful patterns for systems programmers.


Complex initialization

C++ evaluates member initializers as expressions. When the initialization logic is non-trivial, the natural options are a helper function or a ternary chain. An IIFE provides a third option that keeps the logic inline without polluting the enclosing scope with temporary variables.

Initializing a const variable

const int threshold = [&] {
    int base = read_config("threshold");
    if (base <= 0) base = DEFAULT_THRESHOLD;
    return base;
}();

Because threshold is const, the compiler can reason about it more aggressively. Without the IIFE, achieving the same effect requires either a mutable variable or an out-of-line helper.

Initializing a class member

struct Connection {
    int fd_;
    Connection(const char* host, uint16_t port)
        : fd_([&] {
              int s = socket(AF_INET, SOCK_STREAM, 0);
              if (s == -1) throw std::system_error(errno, std::generic_category());
              // set options ...
              return s;
          }()) {}
};

This avoids creating a static member function purely to satisfy the member-initializer-list syntax. Combined with delegating constructors, it eliminates most justifications for those helpers.

The C++ Core Guidelines endorse this pattern explicitly:

ES.28: Use lambdas for complex initialization, especially of const variables.


Forcing cold code out of the hot path

GCC and Clang place code that throws exceptions into a separate cold section (.text.unlikely) automatically, because exception paths are assumed to be rare. Code that handles errors via return values does not get this treatment by default:

if (send_message() == -1) {
    log_error("send failed");
    return;
}

The error-handling block sits inline with the fast path, occupying instruction cache lines that the CPU prefetches on every iteration of the hot loop.

Using [[unlikely]] (C++20)

if (send_message() == -1) [[unlikely]] {
    log_error("send failed");
    return;
}

This hints to the branch predictor and may reorder basic blocks, but does not guarantee the code is placed out of line.

Using an IIFE with GCC attributes

if (send_message() == -1) {
    [&]() __attribute__((noinline, cold)) {
        log_error("send failed");
    }();
    return;
}

The two attributes do distinct things:

  • noinline prevents the compiler from inlining the lambda body back into the caller.
  • cold tells the compiler that this function is unlikely to execute. The compiler optimizes it for size rather than speed, and the linker places it in the cold section of the binary.

The result is that the error handler is physically separated from the hot path. Branch prediction improves because the CPU sees a branch that almost never falls through, and the instruction cache lines used by the hot loop no longer contain error-handling code.

This technique is credited to Matt Godbolt. You can compare the generated assembly with and without the attributes on Compiler Explorer.


Alternatives

ApproachProsCons
Ternary ?:Standard, works everywhereLimited to single expressions
GCC statement expressionsAllows multi-statementNon-standard extension
Out-of-line helper functionAlways worksBreaks locality, extra function declaration
IIFEInline, standard C++11Lambda syntax is unfamiliar to some readers

References