14

This program compiles and runs in C++ but doesn't in a number of different languages, like Java and C#.

#include <iostream>
using namespace std;

void foo2() {
  cout << "foo 2.\n";
}

void foo() {
    return foo2();
}

int main() {

    foo();

    return 0;
}

In Java this gives a compiler error like 'Void methods cannot return a value'. But since the method being called is a void itself, it doesn't return a value. I understand that a construct like this is probably prohibited for the sake of readability. Are there any other objections?

Edit: For future reference, I found some a similar question here return-void-type-in-c-and-c In my humble opinion this question isn't answered yet. The reply 'Because it says so in the specification, move on' doesn't cut it, since someone had to write the specification in the first place. Maybe I should have asked 'What are the pros and cons of allowing returning a void type like C++'?

Community
  • 1
  • 1
Sjoerd
  • 187
  • 1
  • 7
  • you can just use a plain `return;` in a void function if that´s what you are out for. – SomeJavaGuy Apr 04 '16 at 13:58
  • 5
    The only thing a `void` method can return is nothing. `return;` – Elliott Frisch Apr 04 '16 at 13:59
  • Or `foo2(); return;` (although the `return` is unnecessary here). – Andy Turner Apr 04 '16 at 13:59
  • 1
    Well, if `foo2()` doesn't return anything, then semantically what do you expect `return foo2()` to even *mean*? – David Apr 04 '16 at 14:01
  • This is a C (C++?) code, obviously you have to translate it to other language. During `C#` translation `void foo() { return foo2(); }` will become `void foo() => foo2();`. Simple like that. It's *kind of* returning value now. Magic! – Sinatr Apr 04 '16 at 14:04
  • 1
    Well, it's a different programming language, so you'll have to translate the code between them.. Both C# and Java may have taken ideas from C, but they are still individual languages.. The differences may be minimal, but they are still different. I could also ask, why doesn't this C++-code compile in [BrainFuck](https://nl.wikipedia.org/wiki/Brainfuck), which also doesn't really make a lot of sense.. Ok.. I know you are mainly asking why Java and C# can't return something in a void-method, but my question is why CAN you return something in C/C++ when the method is void?.. :S THAT confuses me. – Kevin Cruijssen Apr 04 '16 at 14:33

2 Answers2

20

It's because of the possibility of its usage in templates. C# and Java forbid void as a type argument, but C++ permits it to allow you to write template code like this:

template<typename T, typename TResult>
TResult foo(T x, T y)
{
    return foo2(x, y);
}

If void methods weren't allowed to return a void expression, this template instantiation would be impossible if TResult was void. If that were the case, you would need a separate template definition if you ever wanted TResult to actually be void.

For example, remember how in C# there are two sets of generic general-purpose delegates, namely Func<> and Action<>? Well, Action<T> exists precisely because Func<T, void> is forbidden. The C++ designers didn't want to introduce situations like this wherever possible, so they decided to allow you to use void as a template argument -- and the case you found is a feature to facilitate exactly that.


(Allow me to write the rest in a pretend-Q&A format.)

But why do C# and Java not allow a similar construct?

First, realize how generic programming is made possible in those languages:

Why pick one approach of implementing generic programming over the other?

  • The generics approach maintains the nominal typing of the rest of the language. This has the advantage of allowing the (AOT) compiler to do static analysis, type checking, error reporting, overload resolution and eventually code generation once.
  • The templates approach is essentially duck typing. Duck typing in a nominally typed language doesn't have the advantages described above, but it allows you more flexibility in the sense that it will permit potentially "invalid" things ("invalid" from the point of view of a nominal type system) as long as you don't actually mention those invalid possibilities anywhere in your program. In other words, templates allow you to express a larger set of cases uniformly.

Okay, so what would C# and Java need to do to support void as a valid generic argument?

I would have to speculate to answer this, but I'll try.

At the language level, they would have to waive the notion that return; is valid only in void methods and always invalid for non-void methods. Without this change, very few useful methods could be instantiated -- and they would all probably have to end with recursion or an unconditional throw (which satisfies both void and non-void methods without returning). So to make this useful, C# and Java would also have to introduce the C++ feature of allowing you to return void expressions.

Okay, let's assume you have that and now you can write code like this:

void Foo2() { }
void Foo()
{
    return Foo2();
}

Again, the non-generic version is as useless in C# and Java as it is in C++. But let's move on and see its real usefulness, which is in generics.

You should now be able to write generic code like this -- and TResult could now be void (in addition to all the other types that were already permitted):

TResult Foo<T, TResult>(T a)
{
    return Foo2(a);
}

But remember that in C# and Java, overload resolution happens "early", rather than "late". The same callee will be chosen by the overload resolution algorithm for every possible TResult. And the type checker will have to complain, because you're either returning a void expression from a possibly non-void method or you're returning a non-void expression from a possibly void method.

In other words, the outer method can't be generic, unless:

  1. The callee is also generic and its return type is defined by a generic type parameter that matches that of the outer method.
  2. Overload resolution in generic types and methods is postponed until actual type arguments are made available, so that we can pick a correct non-generic method at the call spot.

What if we went with the first option - make the callee's return type generic and move on?

We could do that, but it simply pushes our problem to the callee.

At some point, we would need some way to "instantiate" some kind of void instance and optionally be able to receive it somehow. So now we would need constructors for void (although every void method could count as a factory method, if you squint) and we would also need variables of type void, possible conversions from void to object, and so on.

Basically, void would have to become a regular type (e.g. a regular empty struct) for all intents and purposes. The implications of this aren't terrible, but I think you can see why C# and Java avoided it.

What about the second option - postpone overload resolution?

Also entirely possible, but note that it would effectively turn generics into weaker templates. ("Weaker" in the sense that C++ templates aren't restricted to typenames.)

Again, it wouldn't be the end of the world, but it would involve losing the advantages of generics that I described earlier. The designers of C# and Java clearly want to keep those advantages.


Sidenote:

In C#, there is one special case I know of, where binding happens after the validation of the generic type definition. If you have a new() constraint on a T and you attempt to instantiate a new T(), the compiler will generate code that checks whether T is a value type or not. Then:

This particular case is very special because, even though it has completely postponed method binding to the runtime, the compiler can still perform static analysis, type checking and code generation once. After all, the type of the expression new T() is always T and a call to something that has an empty formal parameter list can be trivially resolved and verified.

Community
  • 1
  • 1
Theodoros Chatzigiannakis
  • 28,773
  • 8
  • 68
  • 104
  • Thanks for your explanation! Do you think it would be technically possible to implement in Java? – Sjoerd Apr 06 '16 at 08:59
  • @Sjoerd Yes, it would definitely be possible to implement templates in any language, as templates don't require any runtime support. (Although it would be good to have *some* runtime support, to be able to instantiate templates across libraries.) But the way templates work and the semantics they carry don't seem to be well-liked by the designers of Java (or C#, or most languages that I know that have support for generic programming), because they affect the language in subtle ways, outside of what concerns the templates themselves (e.g. this case with `void`). – Theodoros Chatzigiannakis Apr 06 '16 at 09:11
  • 1
    Perhaps worth noting that Java provides the non-instantiable `java.lang.Void` type, which allows you to do `void`-like things with generics. (To make the "return dummy objects" comment a bit more concrete). – Andy Turner Apr 06 '16 at 09:28
  • @Andy I indeed saw the Void class in java.lang which made me think that perhaps it could be autoboxed like an int -> Integer or boolean -> Boolean. – Sjoerd Apr 06 '16 at 12:14
  • @Sjoerd it can't, because 1) there is no void "value"; 2) it can't be instantiated. – Andy Turner Apr 06 '16 at 12:15
4

According to the Java Language Specification §14.17:

A return statement with no Expression must be contained in one of the following, or a compile-time error occurs:

  • A method that is declared, using the keyword void, not to return a value (§8.4.5)

...

A return statement with an Expression must be contained in one of the following, or a compile-time error occurs:

  • A method that is declared to return a value

...

So, by declaring that a method is void, you are saying that it returns no value, so you are limited to using a return; statement with no expression.

Andy Turner
  • 137,514
  • 11
  • 162
  • 243
  • Yes, I understand that it is specified in the JLS, but is there an underlying reason? Since it is possible in C++, but not in Java and C#. The syntax of Java and C# is derived from C / C++, but apparently not this construct? – Sjoerd Apr 04 '16 at 14:20
  • Why do you want to know? If the question is "why can't I do this in Java", the answer is "you just can't, sorry, that's the way it's specified; just move on." If the question is "why *shouldn't* I do this in C++" (since the decision to specify Java like this suggests there might be some issue with it), well, I'm not sure. Somebody more proficient in the internals of C++ would be better to answer that than me. – Andy Turner Apr 04 '16 at 14:36
  • @AndyTurner Yes, somebody could answer, but you have used your powers to close this question as a duplicate of another question (when it isn't). This question specifically asks why C++ can do it while others can't -- there *is* an answer to this. The linked question and its answers aren't quite about that. – Theodoros Chatzigiannakis Apr 06 '16 at 08:01
  • @TheodorosChatzigiannakis reopened. Answer away. – Andy Turner Apr 06 '16 at 08:02
  • @TheodorosChatzigiannakis I think that it was a valid duplicate for the question as written at the time it was closed. OP has edited the question to make the "why is it allowed in C++" much more prominent - the original title was "Is there a reason why a void can't return a void value?". – Andy Turner Apr 06 '16 at 08:12
  • That's true, but in my opinion taking an excerpt of the JLS is a shallow answer. Since it doesn't explain the reasoning for that JLS specification. Also it is probably a defect in my question. – Sjoerd Apr 06 '16 at 08:57