Last week, in a push that purported to eliminate a number of static constructors from the tree, I burned the Mac debug build:
In file included from SVGAnimateMotionElementBinding.cpp:13: In file included from ../../dist/include/mozilla/dom/SVGAnimateMotionElement.h:11: In file included from ../../../content/svg/content/src/SVGMotionSMILAnimationFunction.h:11: In file included from ../../dist/include/nsSMILAnimationFunction.h:15: In file included from ../../dist/include/nsSMILValue.h:10: ../../dist/include/nsSMILNullType.h:47:17: error: constexpr constructor never produces a constant expression [-Winvalid-constexpr] MOZ_CONSTEXPR nsSMILNullType() {} ^ ../../dist/include/nsSMILNullType.h:47:17: note: non-literal type 'nsISMILType' cannot be used in a constant expression
This error message caused some confusion:
18:41 < froydnj> "constexpr constructor never produces a constant expression"? what does *that* mean? 18:42 < philor> sweet, now you know what *every* compiler message sounds like to me
Searching the web for the error was also unhelpful. There was a Qt bug report from a year ago that wasn’t relevant and a smattering of pages discussing just how smart the compiler needs to be to diagnose invalid constant expressions. The closest hit was from a GCC bug on constexpr diagnostics. But even that didn’t quite tell the whole story. So what’s going on here?
I wasn’t sure what a literal type was, so I looked it up. The epsilon-close-to-the-standard draft, N3337, defines the term literal type in [basic.types] paragraph 10 (you’ll usually see this abbreviated to just [basic.types]p10 in discussions or compiler front-end comments; I’ll be abbreviating standard references from here on out):
A type is a literal type if it is:
- a scalar type; or
- a reference type referring to a literal type; or
- an array of literal type; or
- a class type (Clause 9) that has all of the following properties:
- it has a trivial destructor,
- every constructor call and full-expression in the brace-or-equal-initializers for non-static data
members (if any) is a constant expression (5.19),- it is an aggregate type (8.5.1) or has at least one constexpr constructor or constructor template
that is not a copy or move constructor, and- all of its non-static data members and base classes are of literal types.
That bit explains how we have a non-literal type in debug builds: debug builds define a no-op protected destructor. Since that destructor is user-provided and not defaulted, it is non-trivial (in the language of the standard; you can read [class.dtor]p5 for the definition of trivial destructors and [class.ctor]p5 for the definition of trivial constructors). The destructor therefore fails the first property of a literal class type, above. (In debug builds, we define a protected destructor to ensure that nobody is improperly deleting our type. But in opt builds, we don’t define the constructor so that it becomes compiler-defaulted and optimized away properly by intelligent frontends. And that means that any static instances of nsSMILNullType don’t need static constructors.)
OK, but why is having a non-literal type a problem? The compiler ought to simply execute the constructor normally and not worry about the constexpr annotation, right? Except that there is this bit in the definition of constant expressions, [expr.const]p2: “A conditional-expression is a constant-expression unless it involves one of the following as a potentially evaluated subexpression…: …an invocation of a function other than a constexpr constructor for a literal class or a constexpr function”
Aha! Here is our real problem. We are claiming that the constructor of nsSMILNullType is constexpr, but actually executing that constructor in an expression is by definition not a constant expression. And so Clang issues an error, as it should.
Moral of the story: don’t define destructors for classes that you give constexpr constructors to…unless you can default them. Guess we might have to add MOZ_DEFAULT to MFBT after all!