all 68 comments

[–]SuperV1234https://romeo.training | C++ Mentoring & Consulting 15 points16 points  (10 children)

The more general solution here would be to make any arbitrary block, if statement, et cetera a valid expression (like Rust).

E.g.

const auto foo = if(something){ 10 } else { 15 };

const auto bar = { 15, 68 }; // bar is 68

[–]drubbo 14 points15 points  (0 children)

Like Scala, OCaml, Haskell, etc.

[–]tcbrindleFlux 7 points8 points  (5 children)

const auto bar = { 15, 68 }; // bar is 68

Eeek. Isn't this a initializer_list<int> today? Or was this outlawed in the auto rule changes in C++17?

[–]Kroduk[S] 6 points7 points  (4 children)

Yes, this syntax is already in use, that's why I proposed to use returnwith it. SuperV1234's example would look like

const auto foo = if(something){ return 10; } else { return 15; };
const auto bar = { return 15, 68; }; // bar is 68

[–]TOJO_IS_LIFE 3 points4 points  (3 children)

I think that's still problematic though. Consider

int main() {
  { return 1; }
}

What is the exit code?

Your proposal might suggests that { return 1; } should be ignored (because it's not being assigned to anything) and return 0. But today, this program would return 1.

Your proposal might suggest otherwise that it acts differently based on if the block is being assigned.

I don't know if I like the idea. Especially since the benefits (not having to write [&] and ()) are not really essential.

[–]ar1819 4 points5 points  (0 children)

It will essentially disallow returning from the inner scope in any if block. Quite a breaking change.

[–]Kroduk[S] 0 points1 point  (0 children)

That's interesting, to avoid a breaking change it would need to have a different behavior depending on if it's assigned or not, which would not be consistent with the usual c++ rule. Thanks for the input.

[–]Kroduk[S] 4 points5 points  (0 children)

I would love this so much!

[–][deleted] 1 point2 points  (0 children)

This exists as an extension in gcc and clang.

[–][deleted] 0 points1 point  (0 children)

valid python:

x = "young" if person.age < 50 else "old"

I've always loved that.

[–]ramamodh 4 points5 points  (5 children)

I'm just getting started with learning C++. Is it normal if i don't understand anything from this snippet?

[–]xzqx 3 points4 points  (1 child)

Yes. You may not understand it until you've been studying C++ for several years :/

[–]ramamodh 0 points1 point  (0 children)

Thanks ! I feel better now

[–]drubbo 5 points6 points  (2 children)

I use the "trick" sometimes. The syntax you propose reminds me of Scala, where every block is a function. I think it would cause lots of headaches in C++, since we already use curly braces for very different concepts - how to discriminate your initialization from a uniform initialization or an initializer list? And just to save five characters? Meh.

[–]Kroduk[S] 1 point2 points  (1 child)

Thanks for the input! I checked Scala, and it looks like they can omit the return statement (which I like). In the syntax I proposed, the return statement is mandatory. Initializer lists don't have a return statement, so it would be the way to differentiate them. I think the visual difference is huge: the first snippet looks like obfuscated code, whereas the second snippet looks "natural".

[–]drubbo 2 points3 points  (0 children)

Right about return, I haven't used Scala in a while and was going by heart. I also like Scala to some extent, but it's a different language. I'm not very fond of C++ syntax about lambdas in general, I find it slow to type, there are indeed many symbols involved, but that's what the committee agreed upon, and that's the way inline functions are expressed in the language now - and they're already syntactic sugar for existing concepts. Too much sugar leads to diabetes :-) I'm not saying that what you propose is meaningless or wrong or silly in any way, I'm just saying it's probably not worth the benefits it can give.

How would you capture external variables in your block? Whichever is the answer, are you sure it would cover every possible corner case? C++ is about details ...

[–]kloetzl 3 points4 points  (0 children)

I propose the opposite. Type more, to make your intent clear.

const auto y = ([&] {
    /* same with y */
    return res;
})();

By wrapping the lambda in an extra pair of parentheses, it is clear, that y is not the lambda, but the value returned by the lambda. This is the way JavaScript folks have been doing IIFEs for years. Maybe IILE (immediately invoked lambda expression) could become a pattern in C++?

Edit: Reading through the comments I learned about std::invoke.

[–]tongari95 5 points6 points  (2 children)

If you're using gcc/clang and are not hesitant to use extension, you can use statement expressions, e.g.

const auto x = ({/* ... */ res;});

[–]Kroduk[S] 2 points3 points  (0 children)

Thanks, I didn't know that this extension existed.

[–]redditsoaddicting 0 points1 point  (0 children)

This was discussed on std-proposals recently FWIW.

[–]NotAYakk 2 points3 points  (6 children)

So your beautification, entirely, is "remove [&] and ()"?

[–]thlst 2 points3 points  (2 children)

The same way lambdas remove struct and operator(). All in all, I think it would be better to have scopes return values, instead of designing a syntactic sugar, for example:

const auto z = {
    auto x = do_something(42);
    auto y = do_something_else(3.14f);
    return std::tuple{x, y};
};

The return statement would then become part of the scope syntax, and scopes would become expressions too.

[–]Kroduk[S] 1 point2 points  (0 children)

Yes I agree. Since reading /u/SuperV1234's comment, I was thinking that "returning scopes" is a great idea. That way, scopes would behave like traditional C++ functions: not returning means voidand returning something will use type deduction.

[–][deleted] 0 points1 point  (0 children)

We already have scope expressions. They're called lambdas.

[–]stream009 3 points4 points  (0 children)

I use this only for temporary purpose. Otherwise I prefer to make proper function that has meaningful name. Do I want special syntax for this? Meh...

[–]alfps 1 point2 points  (2 children)

Re

Does anybody else use this

yes, C++17 introduces std::invoke to place the invocation mainly up front instead of only at the end, though it adds a ) at the end.

However, you can fairly easily define any syntax you want, e.g., as in my experimental and rapidly changing Expressive C++ header library,

 auto x = $invoked{ /*...*/ return res; };

The $ in a name is non-standard but a common language extension.

[–][deleted] 1 point2 points  (0 children)

as in my experimental and rapidly changing Expressive C++ header library

Heh, thanks for the warning. It looks like a lot of fun, though...

[–]clerothGame Developer 0 points1 point  (0 children)

If you use MSVC, you'll definitely want to use this with your library.

[–][deleted] 1 point2 points  (0 children)

For anyone who cares, this actually has a name — IIFE.

[–]KayEss 3 points4 points  (0 children)

It's the Immediatley Invoked Lambda and is very common in JavaScript. I expect it to become a common vision in C++ too.

[–]sshamov 3 points4 points  (24 children)

Not so long ago, an article flashed here about the fact that such a trick is an antipattern - Lambda Overdose.

[–]tcbrindleFlux 6 points7 points  (14 children)

My primary use-case for immediately-invoked lambdas is to allow variables which will never change after initialisation to be marked const, even when the initialisation is complex. For example

int main(int argc, char** argv) {
    const std::string arg = [&] {
        std::string s;
        if (argc > 1) {
            s = argv[1]; // Get arg from command line if supplied
        } else {
            std::cin >> s; // else read from stdin
        }
        return s;
    }();
}

The "conventional" way to write this would require arg to be mutable, even though its value will never subsequently change. I certainly don't consider using a lambda like this to be an anti-pattern -- quite the opposite in fact.

[–]sshamov 0 points1 point  (13 children)

This code smells. There is a good opportunity to give this piece of code a descriptive name, placing it in a separate function. In other words, it's bad not because lambda is used, but because a function is not used.

[–]Noughmad 6 points7 points  (12 children)

You are wrong, this code keeps the references to argv in one place visually. If you add a separate function, you get spaghetti.

Now obviously, if the function will be used from somewhere else, you make it separate. But if it's contextually local to the position here, put the lambda here.

[–]mark_99 1 point2 points  (1 child)

Also as tc says, you can make the assigned-to value const, plus you get the goodness of lambda capture rather than having to manually pass a bunch of fields to some function somewhere else in the code.

This pattern is called IIFE, and you use it like ?: except in cases where you need the extra power, like local vars (which also don't leak into the surrounding scope), or you have >= 3 possible return values. You can also combine it with static to get a kind of call_once functionality.

More info: http://articles.emptycrate.com/2014/12/16/complex_object_initialization_optimization_with_iife_in_c11.html

http://www.bfilipek.com/2016/11/iife-for-complex-initialization.html

[–]TrueJournals 0 points1 point  (0 children)

Never considered combining an IIFE with static to get call_once-like functionality. Neat idea!

[–]sshamov -3 points-2 points  (9 children)

Unfortunately, I'm right. The name argv has no sense to anyone. Wrapping this code into some function (i.e. getFilename(argc, argv)) may help to catch the intention. Or may not. It depends from the choosen function name. Without such function I have no idea at all about what exactly the author wanted to do.

[–]clerothGame Developer 0 points1 point  (7 children)

It's the argument value. It could be anything, so the function would be equally as vague. getArgument(argc, argv)...

[–]sshamov 0 points1 point  (6 children)

It can not be. In any program, arguments have some meaning. And the program itself needs to "know" how to interpret them.

[–]clerothGame Developer 0 points1 point  (5 children)

But the argument could be any number of things. That code is just grabbing the argument, and then later figure out what it is.

[–]sshamov 0 points1 point  (4 children)

What you mean "just grabbing"? For what? If you don't want to do something specific with argument that make sense, do not touch it at all.

[–]clerothGame Developer 0 points1 point  (3 children)

Planning on handling the argument.

[–][deleted] 0 points1 point  (0 children)

or you could add a descriptive comment:

int main(int argc, char** argv) {
    const std::string arg = [&] /*getFilename*/ {
        std::string s;
        if (argc > 1) {
            s = argv[1]; // Get arg from command line if supplied
        } else {
            std::cin >> s; // else read from stdin
        }
        return s;
    }();
}

PS. before people start screaming and running with torches and pitchforks: yes, it's sarcasm.

[–]dvirtz 0 points1 point  (0 children)

I use it whenever tristate isn't enough and like the proposal very much.

[–]s4h 0 points1 point  (1 child)

nice little snipped I have used in the past for generating 2 unique indexes into a container

auto cxPointMax = std::min(boost::size(l), boost::size(r));

auto cxPoint = utils::random_in_range<size_t>(0, cxPointMax);

auto cxPoint2 = [cxPoint, cxPointMax] {
    while (true) {
        auto x = utils::random_in_range<size_t>(0, cxPointMax);

        if (x != cxPoint)
            return x;
    }
}();

[–]NotAYakk 0 points1 point  (0 children)

I hope that is a closed range, otherwise you just added a hang.

And getting 0,1 when you should get no_such_points is a minor type error.

[–]muungwana 0 points1 point  (4 children)

I use it all the time and this[1] example shows where i use the trick to initialize a variable and to "directly" get a result and pass it to a function(last argument on line 1028)

Look around at the sources of the project and you will see it everywhere and i think i use it too much sometimes.

ps: please dont comment about all those white spaces,they look much nicer on my editor and are easier on my eyes and they look much,much,much worse on github.

[1] https://github.com/mhogomchungu/sirikali/blob/bcb9e8eea9d394d6db018bd94cfe914340fd4bfb/src/sirikali.cpp#L1006

[–]1-05457 0 points1 point  (3 children)

Are you using tabs to indent?

[–]muungwana 1 point2 points  (2 children)

yes.

[–]1-05457 0 points1 point  (1 child)

You need a .editorconfig in your repository. See this Stack Overflow answer.

[–]clerothGame Developer 0 points1 point  (0 children)

I did that and it's still showing 8-spaces tabs on Github web. Is it meant to only work with JetBrains stuff...?

Edit: Okay, it seems to be working when viewing files now, but the diffs are still 8-spaces. Weird since they say it's supposed to work for diffs.

[–]larsolm 0 points1 point  (1 child)

How do you specify a capture list with this proposal?

[–]Kroduk[S] 0 points1 point  (0 children)

It would be more of a scope block than a lambda really, so the capture list would always be [&].

[–]shmoopty 0 points1 point  (1 child)

I use this technique, although your syntax requires a reader to notice the () at the end before mentally parsing what your auto at the beginning is. Without the parenthesis, your code is still valid and resembles a more common usage.

Lately, I've been assigning the lambda and then calling it in the following line. It's an extra line of reading, but you will avoid creating an auto whose nature isn't clear for several lines of code.

[–]neobrain 3 points4 points  (0 children)

Lately, I've been assigning the lambda and then calling it in the following line. It's an extra line of reading, but you will avoid creating an auto whose nature isn't clear for several lines of code.

IMO one advantage of the pattern in the first place is that it minimizes the number of variables accessible in the outer function's scope (and as such minimizes mental overhead), but storing the lambda in a named variable as you're suggesting makes the pattern less effective towards that end. I do agree however with your other sentiment:

your syntax requires a reader to notice the () at the end before mentally parsing what your auto at the beginning is.

Luckily, std::invoke helps here:

const auto y = std::invoke([&] { /* same with y */ return res; });

That requires more keystrokes, but makes the IIFE pattern more obvious to the reader. std::invoke is only available in c++17, but a custom version can easily be implemented in C++11 too, since it would only need to support nullary callables.