9

Consider these code snippets:

Version (1)

void q() {}
class B {
  void f() noexcept(noexcept(q())) {q(); }
  decltype(&B::f) f2;
};

Version (2)

void q() {}
class B {
  void f() noexcept(true) {q(); }
  decltype(&B::f) f2;
};

Version (3)

void q() {}
class B {
  void f() noexcept {q(); }
  decltype(&B::f) f2;
};

All versions of GCC compile these code snippets without any error or warning (including trunk-version). All versions of Clang which support C++17 deny version (1) and (2), but not version (3), with the following error:

<source>:4:16: error: exception specification is not available until end of class definition

  decltype(&B::f) f2;

               ^

Take into account that the standard defines noexcept as equivalent to noexcept(true) [except.spec]. Thus, version (2) and version(3) should be equivalent, which they are not for clang.

Thus, the following questions: At which point do exception specifications need to be evaluated according to C++17 standards? And, if some codes above are invalid, what is the rational behind?


Abstract background for those who are interested:

template <typename F>
struct result_type;

template<typename R, typename C, typename... Args>
struct result_type<R(C::*)(Args...)> {
  using type = R;
}; // there may be other specializations ...

class B {
  int f() noexcept(false) { return 3; }
  typename result_type<decltype(&B::f)>::type a;
};

This code should be valid at least up to C++ 14, because noexcept was not part of the function type (for clang, it compiled up to version 3.9.1). For C++ 17, there is no way to do this.

overseas
  • 1,711
  • 1
  • 18
  • 30
  • Have you tried to full qualify `q`: `noexcept(noexcept(::q()))` ? – Jarod42 May 20 '18 at 14:24
  • 1
    This has no impact. You can even write `noexcept(true)`. I.e., as long as the noexcept contains an expression and is not simply `noexcept`, clang denies compiling. I would be surprized if this is standard-compliant, but I cannot find a source for that. – overseas May 20 '18 at 14:33

2 Answers2

6

This is a result of CWG 1330.

Basically, the class is considered to be complete within its noexcept-specifier (in the resolution of the defect above it's referred to as an exception-specification).

This puts us in a situation where:

void q() {}
class B {
  void f() noexcept(noexcept(q())) {q(); }
//                  ~~~~~~~~~~~~~
//                  evaluated in the context of complete B

  decltype(&B::f) f2;
//~~~~~~~~~~~~~~~
//cannot wait until B is complete to evaluate
};

We need to know decltype(&B::f) to process the declaration of B::f2, but in order to know that type, we need to know the what the noexcept-specifier of B::f is (because now that's part of the type system), but in order to do that, we need to evaluate the noexcept-specifier in the context of the complete B.

The program is ill-formed, clang is correct.

Barry
  • 286,269
  • 29
  • 621
  • 977
  • Thanks! I added an additional example, could you also comment on this? – overseas May 20 '18 at 20:14
  • 1
    @overseas There isn't really a difference between `noexcept(noexcept(q()))` and `noexcept(true)` - the crux is that it's `noexcept(something)`. Just `noexcept` is fine, there's no expression parsing involved whatsoever. – Barry May 20 '18 at 21:22
  • But section 15.4 [except.spec] says: "A noexcept-specification `noexcept` is equivalent to `noexcept(true)`". Further, a "noexcept-specification` is defined as `noexcept` *or* `noexcept(constant-expression)`. This is why I am confused about in which concrete section I find this. I will -re-add the solved-flag as soon as this confusion is resolved :-) – overseas May 20 '18 at 21:52
  • 1
    There's no sane way for the implementation to parse an arbitrary expression without knowing the context. I suppose you can special-case `noexcept(true)` and `noexcept(false)`, but that's even more arbitrary. (This is almost certainly underspecified in the standard. Note that GCC doesn't implement 1330 at all.) – T.C. May 20 '18 at 22:18
  • @T.C. Yes I agree that this is arbitrary. But it is for sure wrong, if the`noexcept(true/false)`-case were invalid, but the `noexcept`-case were valid, because the latter is clearly *defined* by the former. Thus, there seem to be three possible answers: 1) All versions are invalid. 2) All versions are invalid, except `noexcept(true/false)`, which is arbitrary to some degree. 3) All versions are valid, which we already excluded with @Barry answer. Probably it is underspecified, but I would still be interested whether I can rely on any behavior in this case. – overseas May 20 '18 at 22:51
  • I think you are reading too much into the "equivalent" part. It's almost certainly a specification shorthand rather than an intentional "perfect equivalence" design. – T.C. May 20 '18 at 23:03
  • Yes I take this literally because 1) Ithis constant-expression is not discussed separately 2) Consider [except.spec].12. If the intention were to handle `noexcept` and `noexcept(true)` differently, for sure they would not write the comment for `B` like that (they would write `noexcept`). Further there would be an asymmetry, because for most functions you can write nothing (= not `noexcept`), `noexcept`, or `noexcept(...)`, but e.g. for the destructor which is `noexcept` by default, there is no way to get the "not `noexcept`-behavior", only `noexcept(...)`. Why do you think, version 3 is valid? – overseas May 20 '18 at 23:29
  • I just added an "abstract example" why all this may be relevant and in what manner it can limit pre-C++-17 behaviour (at least I suppose it does). – overseas May 20 '18 at 23:53
0

I will answer this question myself with references to all relevant resources.

Let me cite Richard Smith to argue why this is a defect, and why all of the examples are ill-formed, because exception specification are only parsed at the end of the class.

Clang is approximately correct to reject that code.

Per C++ DR1330, exception specifications are not parsed until we reach the end of the class (they can name class members that are declared later, and the class is complete within them), just like member function bodies, default member initializers, and default arguments.

Finally, there's C++ DR361 (still open 16 years after being filed, sadly), wherein the conclusion of CWG is that the program is ill-formed if you use a delay-parsed construct before the end of the class. But we don't have actual standards wording to back that up yet, just a standard that's unimplementable without a fix to that defect.

LLVM Bug 37559, but also see Google Groups, CWG 361, CWG 1330

Further, noexcept should in principal be completely equivalent to noexcept(true), as stated in [except.spec], and it is clearly wrong to accept noexcept, but deny noexcept(true).

Thus, the conclusion is that all examples are ill-formed, even though that is not the desired behavior.

overseas
  • 1,711
  • 1
  • 18
  • 30