C++17 In Detail

21 October 2019

C++ Tricks: IIFE for Complex Variable Initialization

IIFE for complex initialization of const variables in C++

What do you do when the code for a variable initialization is complicated? Do you move it to another method or write inside the current scope?

In this blog post, I’d like to present a trick that allows computing a value for a variable, even a const variable, with a compact notation.

Last Update: 21st October 2019

Intro

I hope you’re initializing most of variables as const (so that the code is more verbose, explicit, and also compiler can reason better about the code and optimize).

For example, it’s easy to write:

const int myParam = inputParam * 10 + 5;

or even:

const int myParam = bCondition ? inputParam*2 : inputParam + 10;

But what about complex expressions? When we have to use several lines of code, or when the ? operator is not sufficient.

‘It’s easy’ you say: you can wrap that initialization into a separate function.

While that’s the right answer in most cases, I’ve noticed that in reality a lot of people write code in the current scope. That forces you to stop using const and code is a bit uglier.

You might see something like this:

int myVariable = 0; // this should be const...

if (bFirstCondition)
    myVariable = bSecondCindition ? computeFunc(inputParam) : 0;
else
    myVariable = inputParam * 2;

// more code of the current function...
// and we assume 'myVariable` is const now

The code above computes myVariable which should be const. But since we cannot initialize it in one line, then the const modifier is dropped.

I highly suggest wrapping such code into a separate method, but recently I’ve come across a new option.

I’ve got the idea from a great talk by Jason Turner about “Practical Performance Practices” where among various tips I’ve noticed “IIFE”.

The IIFE acronym stands for “Immediately-invoked function expression”. Thanks to lambda expression, it’s now available in C++. We can use it for complex initialization of variables.

Extra: You might also encounter: IILE, which stands for Immediately Invoked Lambda Expression.

How does it look like?

IIFE

The main idea behind IIFE is to write a small lambda that computes the value:

const auto var = [&] { 
    return /* some complex code here */; 
}(); // call it!

var is const even when you need several lines of code to initialize it!

The critical bit is to call the lambda at the end. Otherwise it’s just a definition.

The imaginary code from the previous section could be rewritten to:

const int myVariable = [&] {
    if (bFirstContidion)
        return bSecondCondition ? computeFunc(inputParam) : 0;
    else
       return inputParam * 2;
}(); // call!

// more code of the current function...

The above example shows that the original code was enclosed in a lambda.

The expression takes no parameters but captures the current scope by reference. Also, look at the end of the code - there’s () - we’re invoking the function immediately.

Additionally, since this lambda takes no parameters, we can skip () in the declaration. Only [] is required at the beginning, since it’s the lambda-introducer .

Improving Readability of IIFE

One of the main concerns behind IIFE is readability. Sometimes it’s not easy to see that () at the end.

How can we fix that?

Some people suggest declaring a lambda above the variable declaration and just calling it later:

auto initialiser = [&] { 
    return /* some complex code here */; 
};
const auto var = initialiser(); // call it

The issue here is that you need to find a name for the initializer lambda, but I agree that’s easy to read.

And another technique involves std::invoke() that is expressive and shows that we’re calling something:

const auto var = std::invoke([&] { 
    return /* some complex code here */; 
});

Note: std::invoke() is located in the <functional> header and it’s available since C++17.

In the above example, you can see that we clearly express our intention, so it might be easier to read such code.

Now back to you:

Which method do you prefer?

  • just calling () at the end of the anonymous lambda?
  • giving a name to the lambda and calling it later?
  • using std::invoke()
  • something else?

Ok, but the previous examples were all super simple, and maybe even convoluted… is there a better and more practical example?

How about building a simple HTML string?

Use Case of IIFE

Our task is to produce an HTML node for a link:

As input, you have two strings: link and text (might be empty).

The output: a new string:

<a href="link">text</a>

or

<a href="link">link</a> (when text is empty)

We can write a following function:

void BuildStringTest(std::string link, std::string text) {
    std::string html;
    html = "<a href=\"" + link + "\">";
    if (!text.empty())
        html += text;
    else
        html += link;
    html += "</a>";

    std::cout << html << '\n';
}

Alternatively we can also compact the code:

void BuildStringTest2(std::string link, std::string text) {
    std::string html;
    const auto& inText = text.empty() ? link : text;
    html = "<a href=\"" + link + "\">" + inText + "</a>";

    std::cout << html << '\n';
}

Ideally, we’d like to have html as const, so we can rewrite it as:

void BuildStringTestIIFE(std::string link, std::string text) {
    const std::string html = [&] {
        std::string out = "<a href=\"" + link + "\">";
        if (!text.empty())
            out += text;
        else
            out += link;
        out += "</a>"; 
        return out;
    }(); // call ()!

    std::cout << html << '\n';
}

Or with a more compact code:

void BuildStringTestIIFE2(std::string link, std::string text) {
    const std::string html = [&] {
        const auto& inText = text.empty() ? link : text;
        return "<a href=\"" + link + "\">" + inText + "</a>";
    }(); // call!

    std::cout << html << '\n';
}

Here’s the code @Coliru

Do you think that’s acceptable?

Try rewriting the example below , maybe you can write nicer code?

Benchmark of IIFE

With IIFE, we not only get a clean way to initialize const variables, but since we have more const objects, we might get better performance.

Is that true? Or maybe longer code and creation of lambda makes things slower?

For the HTML example, I wrote a benchmark that tests all four version:

@QuickBench

And it looks like we’re getting 10% with IIFE!

IIFE C++ Benchmark
Some notes:

  • This code shows the rough impact of the IIFE technique, but it was not written to get the super-fast performance. We’re manipulating string here so many factors can affect the final result.
  • it seems that if you have less temporary variables, the code runs faster (so StringBuild is slightly faster than StringBuild2 and similarly IIFE and IIFE2)
  • We can also use string::reserve to preallocate memory, so that each new string addition won’t cause reallocation.

You can check other tests here: @QuickBench

It looks like the performance is not something you need to be concerned with. The code works sometimes faster, and in most of the cases the compiler should be able to generate similar code as the initial local version

Summary

Would you use such a thing in your code?

In C++ Coding Guideline we have a suggestion that it’s viable to use it for complex init code:

C++ Core Guidelines - ES.28: Use lambdas for complex initialization,

I am a bit sceptical to such expression, but I probably need to get used to it. I wouldn’t use it for a long code. It’s perhaps better to wrap some long code into a separate method and give it a proper name. But if the code is 2 or three lines long… maybe why not.

Also, if you use this technique, make sure it’s readable. Leveraging std::invoke() seems to be a great option.

I want to thank Mariusz Jaskółka from C++ Polska for the review, hints about compacting the code and also perf improvements with reserve().

Your turn

  • What do you think about such syntax? Have you used it in your projects?
  • Do you have any guidelines about such thing?
  • Is such expression better than having lots of small functions?

BTW: maybe I should ask Java Script guys since this concept comes from their world mostly :)

References

C++17 In Detail
© 2017, Bartlomiej Filipek, Blogger platform
Disclaimer: Any opinions expressed herein are in no way representative of those of my employers. All data and information provided on this site is for informational purposes only. I try to write complete and accurate articles, but the web-site will not be liable for any errors, omissions, or delays in this information or any losses, injuries, or damages arising from its display or use.
This site contains ads or referral links, which provide me with a commission. Thank you for your understanding.