C++ Weekly - Ep 289 - Returning From The `void`

preview_player
Показать описание
☟☟ Awesome T-Shirts! Sponsors! Books! ☟☟

T-SHIRTS AVAILABLE!

WANT MORE JASON?

SUPPORT THE CHANNEL

GET INVOLVED

JASON'S BOOKS

► C++23 Best Practices

► C++ Best Practices

JASON'S PUZZLE BOOKS

► Object Lifetime Puzzlers Book 1

► Object Lifetime Puzzlers Book 2

► Object Lifetime Puzzlers Book 3

► Copy and Reference Puzzlers Book 1

► Copy and Reference Puzzlers Book 2

► Copy and Reference Puzzlers Book 3

► OpCode Puzzlers Book 1


RECOMMENDED BOOKS

AWESOME PROJECTS

O'Reilly VIDEOS

Рекомендации по теме
Комментарии
Автор

I would strongly recommend against this in this form. Most people tend to think of adding a return value to a function which previously returned void as a non-breaking change. However, if you use this pattern, you'll be broken.

For example:

return vec.emplace_back(...);

emplace_back() was changed to return a reference to the added element in C++17. If you return the void value as above, your code breaks in C++17.

If you like this pattern, e.g. in the switch-case example, I recommend explicitly casting to void to avoid this backwards compatibility issue:

return (void)vec.emplace_back(...);

quincunx
Автор

This is something I have strong feelings about. The worst bug I ever checked into production code came about partially because of this.

ex:
void func();
void other_func();

void func()
{
if(condition)
return


other_func();
}


See the missing semicolon after return. C++ doesn't care about whitespace and can return void so it ended up calling a function that I explicitly tried to avoid calling by this condition. The end result was memory corruption eventually. I'm the reason our coding guidelines require curly braces around early returns from functions. My opinion on coding practices is that you should try to avoid situations where an easy typo can drastically change the compiled output.

DaRealPielover
Автор

It absolutely makes sense! The keyword here is compositionality (composability). Functions compose, statements don't. Returning 'void' when you need it is equivalent to returning the singleton set ("type") in Category Theory.

EvanBC
Автор

It’s also useful when writing wrappers for other functions. When the dev decide to change the returning type of wrapped function then the compiler will complain, instead of silently ignore the returned value.

dm
Автор

"if you were paying attention in the episode..." I'm mildly offended! I always pay attention to C++ Weekly! I even start to look forward to it on Sunday night!

treyquattro
Автор

There is a push to make void a regular type so that it would allow for more uniform syntax with templates (since currently you often have to special case handling for void). This ability to return void gives us just a taste of what we can do. Making void a fully regular type would benefit more than just templates as well, it'd allow swapping in return values by just altering a typedef, for example.

N....
Автор

I wouldn't push back on this in code review. Someone may question whether or not it compiles, but I don't think there is much chance of confusion about what this will actually do if it does compile, so I don't see much danger in it.

mCoding
Автор

When wondering, say, what the exact type that call(return_something_else) returns I tend to use the following trick:

```
template <typename> struct Foo;

int main() {

}
```
Then I get an error that I’m creating an instance of undefined type `Foo<int>` or `Foo<int&>` or whatever and so can read out the type.
It also works when there are type aliases and templates to print out the actual type for some type.

aDifferentJT
Автор

I agree it makes sense in the template/`auto` case. But in the other cases I’m not sure what the advantage is over just `do_something(); return;`.

danielrhouck
Автор

I used return void call switch for it's very compact form in a very long switch statement. It nicely promoted a pattern to have everything to be done in a case being externalized into a separate function. Has also been extended by colleagues without a problem.

You can obviously have this by using the regular case, call and break pattern, but break is usually formatted to be on the next line. However I would only use it for very lo g switches...

xmoex
Автор

Returning void functions seems like a perfectly reasonable thing to do (especially if you have comprehensive tests). Ultimately, as long as a program is literate, documented, and tested, I’m happy with it.

SirMrMcMsMrs
Автор

I like using void return too.
I think it gives the code a uniform and consistent look in some cases.

sera_kath
Автор

The `return some_fun()` from a case scenario is perfectly reasonable. Quite readable, and much briefer than using a break for every case, at least for such a simple implemention.

Phroggster
Автор

I have used this feature: I have a message handler that is templated on a key type, a message type, a way to get a key from a message, and a function to pass the message to. The idea is that you can say for example, "messages where a field has this key are routed to this function, and messages where a field has this other key are routed to this other function." However, this needs to return the result of the function that is called. If it weren't possible to return void in this way, I would need to specialize the entire thing just to handle that. But instead it has the same code whether the message handler returns a value or not. It's quite neat.

But in non-templated situation where it was 100% always returning void, I would probably choose not use it because it defies the principle of least astonishment to me. "do_something(); return; is more idiomatic than "return do_something();" in a void context.

MatthewChaplain
Автор

One of the possible cases for this is writing template functions. If the return type is a template type, then you want void treatable as if int.

stephenhowe
Автор

I love the mini comentators, those are cute, someone has been playing with blender

fcolecumberri
Автор

Yes, I've written code kinda like your "call_a_func" before. I used it in a CPU instruction simulator. The case values were opcodes and the functions executed a simulation of the opcode. I did this because I was being lazy-- I knew the compiler would take the switch and generate a (hopefully optimal) jump table from it.

johnpassaniti
Автор

I actually had a use case for this recently, where I wanted to wrap some functionality around a parameter that is callable. I did strange SFINAE stuff to handle the case when the function returns a value vs. when it doesn't. Later, I successfully merged them back into one implementation after I learned that I can successfully do this. In such a use case, it's not a break when the function is changed to return a value, because this wrapper is to return whatever type its callable parameter is supposed to return.

It was the first (and perhaps the last) time I found a need for this feature of the language.

mikebentley
Автор

I've done this often enough, and sometimes just feels like the right thing to do, but at the same time looks a bit odd. I don't recall all the exact circumstances in which I've done this though, definitely done it in switch statements to reduce number of lines due to "break;".

saulth
Автор

Before using that strange switch statement in the call_a_func function, I would test how it compares to using an enum value for the parameter where the enum value is used as an index into a table of function pointers (or std:function's ). If the switch is small, then it may be quicker, but as the switch grows, there will be a number of comparison machine instructions that would slow it down.

ariyowalker