5

I have several void functions (lets call them foo and bar) that share the same void function cleanup that, well, cleans up after them if they mess up:

#include <iostream>

void cleanup() { std::cout << "doing cleanup" << std::endl; }

void foo(int & i) {
    if(i == 0) { return cleanup(); }
    --i;
    if(i == 0) { return cleanup(); }
    ++i;
}

void bar(int & i) {
    if(i == 0) { return cleanup(); }
    ++i;
    if(i == 0) { return cleanup(); }
    --i;
}

int main() {
    int i = 0;
    foo(i);
    bar(i);
    return 0;
}

cpp.sh happily compiles and runs the code.

Thanks to the answer to this question I know that I can return an object of type void. But I don't know if it applies to returning void return values.

What do you think, does the code comply with the standard?

And would you rather return dummy integers to make the code more readable? Or would the useless return values make the code harder to read?

edit: I feel like I need to add some clarification as to why e.g. 'cleanup(); return;' is not a solution. The actual code is more complex than the example and depending on where I leave the function some other stuff happens after the cleanup() call. 'return cleanup();' is just a convenient way of not putting all the stuff behind it in conditionals in instances where I can/have to leave immediately.

Community
  • 1
  • 1
a.peganz
  • 318
  • 1
  • 4
  • 11
  • 4
    *"Thanks to the answer to this question I know that I can return an object of type `void`."* There are no objects of type `void`, and a function with return type `void` never returns anything but control. – Baum mit Augen May 03 '16 at 16:40
  • 5
    Why not just `{ cleanup(); return; }`? – PaulMcKenzie May 03 '16 at 16:42
  • 2
    Or use RAII so you do not have to write cleanup code. – NathanOliver May 03 '16 at 16:44
  • @Baum: maybe 'object' is not the correct name, I just couldn't come up with a better name for the 'void()' in 'return void();' – a.peganz May 03 '16 at 16:48
  • Was that a downvote raid on the questions and both answers ? – Quentin May 03 '16 at 16:51
  • @Paul, Nathan: the actual code is more complex and depending on where I leave the function some other stuff happens after the cleanup() call. 'return cleanup();' is just a convenient way of not putting all the stuff behind it in conditionals in instances where I can/have to leave immediately. For the same reason RAII is not applicable, neither the real foo() nor the real bar() allocate resources or initialize objects, and cleanup() does not deallocate resources. – a.peganz May 03 '16 at 16:52
  • 1
    @a.peganz You can place that some other stuff in a RAII type, then when the function exits it is destroyed and the code runs. This way the code manages itself and you do not have to remember to call `cleaup` at every exit point. – NathanOliver May 03 '16 at 16:59
  • 1
    @a.peganz RAII is not just about allocating and destroying resources. If you need to run some code, any code, and you want this code to run at the function return, regardless of why that function returns, RAII can be used to do this. – PaulMcKenzie May 03 '16 at 17:04

3 Answers3

13

From n4582

6.6.3 The return statement [stmt.return]

Paragraph 2

The expr-or-braced-init-list of a return statement is called its operand. A return statement with no operand shall be used only in a function whose return type is cv void, a constructor (12.1), or a destructor (12.4). A return statement with an operand of type void shall be used only in a function whose return type is cv void. A return statement with any other operand shall be used only in a function whose return type is not cv void; the return statement initializes the object or reference to be returned by copy-initialization (8.5) from the operand.

Questions:

But I don't know if it applies to returning void return values.

Yes its perfectly valid to return a void expression from a function that return void. This becomes very handy in templated code so you don't have to special case void functions.

What do you think, does the code comply with the standard?

Yes absolutely.

And would you rather return dummy integers to make the code more readable? Or would the useless return values make the code harder to read?

That's totally up to you and your aesthetics. Does it make the code look more readable to you (or do you have coding guidelines that you need to follow). Personally I would not return dummy values (as the user may expect some meaning from them).

Community
  • 1
  • 1
Martin York
  • 257,169
  • 86
  • 333
  • 562
  • It is still a mistery to me how so many people on so seemingly know the C++ standard by heart. That is exactly the answer I was looking for. Also thank you @BenVoigt for providing a very nice example on where this can be applied. – a.peganz May 03 '16 at 17:06
  • 2
    @a.peganz: Definitely don't know the standard by heart. But it is well organized so finding stuff is not hard. Just use the latest version when looking up stuff: http://stackoverflow.com/a/4653479/14065 – Martin York May 03 '16 at 17:08
5

This is useful for perfect forwarding. Imagine that you have a functor of type (template-parameter), so the return type can potentially be anything. This permission to return a value of type void allows you to call the functor and expose its return value to your caller, without having to write separate code for the case of no return value.

template<typename Functor, typename... Args>
auto forward(Functor what_to_call, Args... args) -> decltype(what_to_call(std::forward<Args>(args)...))
{
    return what_to_call(std::forward<Args>(args)...);
}

That's already pretty messy, but if not for the ability to propagate void return types using the return keyword, you'd need two variations controlled by enable_if.

template<typename Functor, typename... Args>
auto forward(Functor what_to_call, Args... args) -> enable_if<is_same_type<decltype(what_to_call(std::forward<Args>(args)...)), void>::value, void>::type
{
    what_to_call(std::forward<Args>(args)...);
    return;
}

template<typename Functor, typename... Args>
auto forward(Functor what_to_call, Args... args) -> enable_if<!is_same_type<decltype(what_to_call(std::forward<Args>(args)...)), void>::value, decltype(what_to_call(std::forward<Args>(args)...))>::type
{
    return what_to_call(std::forward<Args>(args)...);
}

So, return function_returning_void(); is designed for special circumstances where it is useful. Don't feel that you have to return a dummy object of type void or any other type everywhere just because it is possible. For example, don't do return void(); or return -1; when simply return; will do.

Ben Voigt
  • 277,958
  • 43
  • 419
  • 720
2

What do you think, does the code comply with the standard?

Yes, it does. In a function returning void, you may use an expression in a return statement if the type of the expression is void. Which it is, if it is a call to a function that also returns void.

And would you rather return dummy integers to make the code more readable? Or would the useless return values make the code harder to read?

I don't think dummy integers would make the code more readable. Programmers expect return values to be meaningful.

What I think would be more readable, is to split the function call and the return to separate statements:

cleanup();
return;

Although I admit, that's entirely a matter of opinion.

eerorika
  • 232,697
  • 12
  • 197
  • 326
  • A missing expression isn't an expression of type `void`. Other than that I agree completely. – Ben Voigt May 03 '16 at 16:57
  • @BenVoigt I now rolled the text back to previous version, where I made no such claim. For some reason, I figured that it would be simpler to formulate the syntax if missing expressions did return `void` and assumed that it was the case. I stand corrected. – eerorika May 03 '16 at 17:03