C++ quiz of the day

struct VD2 {};
struct VD2 final;

Is this code:

  • ill-formed, requiring a diagnostic from the compiler?

  • ill-formed, no diagnostic required?

  • valid, and further inheritance from VD2 is diagnosed as an error?

  • valid, and further inheritance from VD2 is 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 noreturn specifies 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 noreturn attribute if any declaration of that function specifies the noreturn attribute.

If a function is declared with the noreturn attribute in one translation unit and the same function is declared without the noreturn attribute in another translation unit, the program is ill-formed, no diagnostic required.

If a function f is called where f was previously declared with the noreturn attribute and f eventually 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 VD2 is 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 {};
Posted 2018-07-08