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:
noinlineprevents the compiler from inlining the lambda body back into the caller.coldtells 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
| Approach | Pros | Cons |
|---|---|---|
Ternary ?: | Standard, works everywhere | Limited to single expressions |
| GCC statement expressions | Allows multi-statement | Non-standard extension |
| Out-of-line helper function | Always works | Breaks locality, extra function declaration |
| IIFE | Inline, standard C++11 | Lambda syntax is unfamiliar to some readers |
References
- C++ Core Guidelines, ES.28: http://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#Res-lambda-init
- GCC Common Function Attributes: https://gcc.gnu.org/onlinedocs/gcc/Common-Function-Attributes.html
- Matt Godbolt, “Forcing code out of line in GCC and C++11”: https://xania.org/201209/forcing-code-out-of-line-in-gcc