C++ quiz of the day
struct VD2 {};
struct VD2 final;
Is this code:
-
ill-formed, requiring a diagnostic from the compiler?
-
valid, and further inheritance from
VD2is diagnosed as an error? -
valid, and further inheritance from
VD2is permitted with no diagnostic?
The answer is below the break.
I kind of backed into this interesting question while coming up with the wording for
my soon-to-be-proposed attribute [[trivially_relocatable]]. The prior art that I’m
copy-and-pasting in my Clang patch is [[clang::trivial_abi]],
but the closest prior art in the actual Standard seems to be [[nodiscard]].
Its wording is not as painstakingly
thorough as the subsequent wording for [[noreturn]],
though, so I’m mostly copying the latter.
The attribute-token
noreturnspecifies that a function does not return.It shall appear at most once in each attribute-list and no attribute-argument-clause shall be present.
The attribute may be applied to the declarator-id in a function declaration.
The first declaration of a function shall specify the
noreturnattribute if any declaration of that function specifies thenoreturnattribute.If a function is declared with the
noreturnattribute in one translation unit and the same function is declared without thenoreturnattribute in another translation unit, the program is ill-formed, no diagnostic required.If a function
fis called wherefwas previously declared with thenoreturnattribute andfeventually returns, the behavior is undefined.
My wording copies essentially all of these conditions, making the obvious substitutions, so that for example
class [[trivially_relocatable]] Widget;
class [[trivially_relocatable]] Widget { ... };
is permitted, as is
class [[trivially_relocatable]] Widget;
class Widget { ... };
but not
class Widget;
class [[trivially_relocatable]] Widget { ... };
and certainly not
class Widget { ... };
class [[trivially_relocatable]] Widget;
I was also investigating whether the set of “trivially relocatable by default”
class types should include a class with a virtual destructor if that virtual
destructor is both defaulted and final (or, more sensibly, if the class itself
is final — please don’t put final on the destructor of a non-final class type!),
which I eventually decided was not worth the pain of specifying. But that’s what
got me looking at the finer points of final.
Anyway, back to the quiz.
struct VD2 {};
struct VD2 final;
Recall that “final” is a contextual keyword in C++11;
it carries special meaning only in certain contexts. In all other contexts it
acts as a plain old identifier.
This code defines a non-final struct type VD2, and then it defines a global
variable of type VD2 that happens to be named final. Therefore the correct
answer is #4:
- valid, and further inheritance from
VD2is permitted with no diagnostic
I’m as surprised as you are!
For extra credit, identify the behavior of this snippet in C++:
struct VD3 { int i; };
struct VD3 final{ 42 };
For foreign-exchange student credit, identify the behavior of this snippet in C, which comes via Stefan Schulze Frielinghaus:
struct VD4 final;
struct VD4 {};
