Speaking specifically about C++, lambdas are good for this, if not quite syntactically ideal:
AwesomenessT largerFunction(Foo1 foo1, Foo2 foo2)
{
ResultT1 result1 = [&] {
Bar bar = barFromFoo1(foo1);
Baz baz = bar.makeBaz();
return baz.awesome();
} ();
ResultT2 result2 = [&] {
Bar bar = barFromFoo2(foo2);
return bar.awesome();
} ();
return result1.howAwesome(result2);
}
Bonus: you can initialize 'const' variables with multiple statements:
const auto values = [&] {
std::vector <int> v (n);
std::iota (begin (v), end (v), 0);
std::shuffle (begin (v), end (v), std::mt19937 {seed});
return v;
} ();
I've been taking this tack more and more as well and while the syntax is never elegant, one at least grows used to it.
I also try to explicitly name which variables I'm capturing (within reason) as it makes it obvious at a glance what can and can't be modified within the lambda. I really wish it was possible to force constness on captured variables :/
I've experimented with this, too. One thing that I also like is that you can have multiple returns within the lambdas and know that the control flow paths will merge again at a common point. The compiler can also make sure that each one returns a value of a compatible type.
I've found myself doing this a lot. Glad to know I'm not the only one. Functions with a single call site don't need to be cluttering up a larger namespace; limited scope is also good for the internal variables.
Why the hell would you want to do that? There's no benefit and only drawbacks over the plain old block syntax.
> Why the hell would you want to do that?
To isolate all of the initialization logic for a single object (e.g., "result1" or "result2") into its own scope. You could use a function (in fact, that's exactly what's being done here—it's just an anonymous function), but moving that logic away from its use and into global scope generally just makes the code more difficult to understand.
> There's no benefit and only drawbacks over the plain old block syntax.
You cannot initialize a const object with the "plain old block syntax." With this, you can, and that is an enormous benefit. It's also much easier to see that the object is initialized; it's not immediately obvious that an uninitialized variable declaration followed by an assignment many lines later will always initialize the variable, but if you initialize it in its declaration, you know it will necessarily be initialized. That is also a very important benefit.
What are the drawbacks? That the syntax is a little uglier? It's not ideal, but it's hardly huge syntactic overhead: four or five non-whitespace characters and a return instead of an assignment. In fact, the syntax is markedly better if you have multiple initialization paths, because you can use return instead of goto; compare
to
Even after the "plain old block" version has been contorted to make assignment to foo unconditional (it's essentially a manually inlined function), it isn't obvious that it's correct. Does the block do "goto the_next_thing" or something similarly hairy? Read the whole thing to find out.
In contrast, once you've seen that foo is initialized by a function and that function ends in an unconditional return, you know that foo cannot not be initialized at the_next_thing, period (even if you throw exceptions into the mix!).
You might argue that you can verify the correctness of the first version, and of course you can, just as you can verify the correctness of an HTML parser written in brainfuck. It's all a matter of cognitive load, of the amount of context you have to hold in your head at one time to reason about the correctness of one part of your code. It's the difference between verifying that the function initializing foo has an unconditional return and verifying that every control path in the initialization block reaches foo_init, when what you're really trying to do is just to show that foo will point to a valid string at "the_next_thing."
In fact, that's basically the primary motivation behind functions, next to code reuse. All this pattern is really doing is modularization-by-function of initialization logic without the cognitive overhead induced by lexically separating the logic's definition from its use. In doing so, it delivers the same advantages as the plain-initialization-block pattern, but preserves the comparatively simple control-flow-barrier semantics of functions.
Compared to goto-spaghetti, typing "[&]" and "();" really isn't that big of a deal.
I'm new to C++ so please forgive my ignorance but where is howAwesome() defined in result1's lambda?
It isn't. It's a part of the type "ResultT1."
Those lambdas are immediately applied, so it's defined in the type returned by them.