16

A little known, but almost never used C++ feature is given a declaration:

void foo();

One possible, legal definition could be:

void foo() try {
  throw 42;
}
catch(...) {
}

Here the whole function implementation wrapped is within a try/catch pair, which seems to be similar to allowing this.

Is that legal to do for int main()? E.g.:

int main() try {
  throw 42;
}
catch(...) {
}

The rules for main, n3290 § 3.6.1 mostly talk about what arguments it should take and what it returns - they don't seem to explicitly forbid it as they do with various other odd things (e.g. linkages) you might be tempted to try.

Is this legal and well defined?

Community
  • 1
  • 1
Flexo
  • 87,323
  • 22
  • 191
  • 272
  • 2
    Interesting academic question, although I'm not sure it has much practical use. http://stackoverflow.com/a/620817/10077 – Fred Larson Dec 06 '11 at 19:20
  • Legal? Technically most compilers will support it. Well defined? Not really as I certainly can't think of any sane reason to do this. – AJG85 Dec 06 '11 at 19:22
  • @AJG85 - I meant well defined in the sense of "invoking neither undefined behaviour, nor implementation defined behaviour", not in the "well tested in common implementations" way – Flexo Dec 06 '11 at 19:23
  • @awoodland: A well in that case as you pointed out the standard doesn't say much one way or another. It will be left up to the compiler implementation and thus will vary greatly. – AJG85 Dec 06 '11 at 19:25
  • Technically if it's not "implementation defined behavior" doesn't that automatically make it "undefined behavior"? – Mr. Llama Dec 06 '11 at 19:27
  • 1
    @AJG85 - if it's left up to the compiler then it's implementation defined and would be in the "Index of implementation-defined behavior" at the back of the standard. – Flexo Dec 06 '11 at 19:28
  • @GigaWatt - by my reading it's probably fine, it's fine in the general case and there's no special case rules that I've found yet that apply to it. It's the absence of a special case rule for `main` that surprised me though. – Flexo Dec 06 '11 at 19:30
  • @awoodland: Are all compiler extensions for each known implemented environment listed? I suspect there is some assumptions being taken advantage of to support something like this. I can't find anything explicitly related to this though. – AJG85 Dec 06 '11 at 19:37
  • Amusing question :) It may have been an omission for I am surprised, in case it is allowed, that there is no precision upon the returned value in the `catch` clause. It may be covered by the fact that the return value "defaults" to 0... dunno – Matthieu M. Dec 06 '11 at 19:43
  • What is the exact question? Whether or not a function uses a function-try-block is an implementation detail, how does this affect the rules for `main`? – Kerrek SB Dec 06 '11 at 19:51
  • @KerrekSB - The exact question is "should every conforming implementation allow `int main() try { //...`?" - one way of proving that it is allowed would be if a function-try-block was just an implementation detail and main isn't a special case. The opposite proof would be an explicit rule for main. – Flexo Dec 06 '11 at 19:54
  • @MatthieuM. since starting from C++11, `=default`, `=delete`, `{ ... }` and `try { ... } catch...` all are forms of *function-body*, which the spec says is denoted by informal uses of "body of a function X", I strongly vote for interpreting the spec to mean that a catch block of a function try block on main defaults to `return 0;`. – Johannes Schaub - litb Dec 06 '11 at 21:58

2 Answers2

8

The standard does not forbid its usage within [basic.start.main], and, while forcing all implementations to support at least int main() {/*...*/ } and int main(int argc, char* argv[]) {/*...*/}, does not limit implementations to those two declarations (3.6.1, para. 2).

From that in isolation, it would appear at the least that it is legal, though of course that relates only to function-declarations, not function-definitions.

Reading on, [except.handle], paragraph 13 states the following:

Exceptions thrown in destructors of objects with static storage duration or in constructors of namespace-scope objects are not caught by a function-try-block on main(). (15.3 para. 13)

It makes specific mention of a function-try-block placed on main(), which strongly implies that such a structure is legal and has defined behavior. Adding in the information that main() is only special in its name and return type, and that implementations may not overload it to alter any behavior, makes a pretty strong case that it acts in a normal fashion except when specially noted such as in the above quote. In other words, yes, it is legal and well-defined.

The blog post I supplied in the first version of this answer actually does a good job of illustrating the rules given by the above blockquote, so I'll retain the link to it, even though it does not directly discuss the issue in the OP's question.

Regarding a comment on the OP, you can issue return statements within a function-try-block, and [except.handle] has this to say:

Flowing off the end of a function-try-block is equivalent to a return with no value; this results in undefined behavior in a value-returning function (6.6.3). (15.3 para. 15)

If you're in a catch-block at the end of main, you're not going to flow over the function's body (which would be the try-block in this case), so the rule that main automatically calls return 0; on flowover doesn't apply. You need to return some int (quite possibly an error code) to keep from becoming undefined.

matthias
  • 2,419
  • 1
  • 18
  • 27
  • 1
    Not much information (on the definedness), the `static` constatations are rather simple: "global" objects are initialized before `main` is called and destructed after it has returned... so obviously not within the `try/catch` block. As for the remarks on the constructor syntax, yes it is weird but does not really answer the question either... – Matthieu M. Dec 06 '11 at 19:41
  • I agree with that assessment, and have noted it; thanks. I found an explicit reference to `main()` having function-try-blocks, so I think the article is pretty useless anyway. I'll be making an edit to clarify this. – matthias Dec 06 '11 at 20:06
  • That quote from 15.3 is pretty interesting. (That was possibly going to be my next question if it was legal) Combined with the DR that Johannes linked to that seems to answer it as allowed. – Flexo Dec 06 '11 at 20:20
  • 1
    Deleting my answer.. need to investigaze at home... but what sense does it make to give two required-to-be-acceptable definition forms of main if not with the implication that all other definition forms are *not* required to work? – Johannes Schaub - litb Dec 06 '11 at 20:23
  • Defining two must-accept definitions solidifies two minimum-functionality "best practices" that developers can know they can be compatible with (else various compiler vendors would all create their own special way of handling args) while allowing vendors to experiment with new ideas as tech progresses. The standard can add any new proven good ideas to future editions without too much surprise. Imagine a platform that allowed you to register for accepted arguments (ala LLVM's CommandLine lib) and parsed those for you before main as an object more complex than `char*[]`. – matthias Dec 06 '11 at 20:35
  • @awoodland: After thinking about it, I'm pretty sure 15.3 p.15 doesn't even need to supersede `main`'s auto-`return 0;`. In such a case, the try-block is the body of the function, and you're clearly not going to reach the end of the body if you've thrown/caught. `main` is nothing special except when explicitly stated, so you'd have to return an `int` of some kind within the catch-block. – matthias Dec 06 '11 at 20:39
  • Are you saying that all impl must accept a function try block, or are you saying that impls are merely allowed to accept (or reject) a function try block? – Johannes Schaub - litb Dec 06 '11 at 21:00
  • Based on the fact that the standard specifically defines some behavior of a function-try-block on `main`, I would say that all impls must accept it. Beyond that, it's not even really a function declaration issue; by the standard, any function-definition can use a function-try-block instead of a function-body (8.4 para. 1). – matthias Dec 06 '11 at 21:10
  • 2
    As of c++11 a functiontryblock *is* a function body :-) – Johannes Schaub - litb Dec 06 '11 at 21:13
  • Oh excellent! I'm working from a copy from 2005, and was actually wondering why it differentiated between the two. I should go grab the 11 standard and double-check everything--I doubt anything would have been meaningfully changed, but at the very least my section numbers might be wrong now. – matthias Dec 06 '11 at 21:25
0

I have tried it, it compiles, and it runs as expected. A peculiar formulation, but I don't think it breaks any rules. For clarity (for yourself and future code mantainers), you could also rephrase it as:

int main() 
{
    try {
      throw 42;
    }
    catch( int /*...*/) {
    }
}
alexandreC
  • 481
  • 5
  • 13
  • 4
    It works on my compiler too. The problem with "it compiles and runs" is I know my compiler compiles and runs a lot of things that aren't well defined. – Flexo Dec 06 '11 at 19:24
  • 1
    Fair point, @awoodland. So, in doubt, I would suggest using the formulation I mention above, which seems to do exactly what you require. Your original formulation does not seem to break any rules from the standard though. – alexandreC Dec 06 '11 at 19:41