Does the standard allow an implicit virtual destructor not being implicitly defined when no instances of its class are created?

Viewed 256

While thinking about this question, I stumbled upon something else I don't understand.

Standard says...

[class.dtor]/4

If a class has no user-declared destructor, a destructor is implicitly declared as defaulted. An implicitly-declared destructor is an inline public member of its class.

[class.dtor]/10

[...] If a class has a base class with a virtual destructor, its destructor (whether user- or implicitly-declared) is virtual.

[class.dtor]/7

A destructor that is defaulted and not defined as deleted is implicitly defined when it is odr-used or when it is explicitly defaulted after its first declaration.

[basic.def.odr]/3

[...] A virtual member function is odr-used if it is not pure. [...]

So now I'm wondering whether this code should compile:

#include <memory>

struct Base {
    virtual ~Base() = default;
};

struct Bar;
struct Foo : Base {
    std::unique_ptr<Bar> bar_{};
};

https://godbolt.org/z/B0wvzd

I would think that ~Foo() has to be implicitly defined, because it is virtual, but it would not compile because Bar is incomplete in this TU. Yet the code compiles in all major compilers.

What am I missing?

1 Answers

So now I'm wondering whether this code should compile:

Are you "wondering" whether this complete program "should" compile?

void f();
void g() { auto ff = &f; }
int main() {}

If you assume it will compile, "how many times" do you conclude the code will compile? (Is it a single truth, or a double independent truth, that it should compile?)

  1. f() is "used" in a non-use way (the address is assigned to a local variable that is optimized away).
g():
        ret
  1. g() itself isn't even used

Any "dumb" linker could see that because g() isn't needed, f() isn't needed; that is assuming a "dumb" compiler would assume that f() is even needed in g()!

Yet an undeclared function (f()) was clearly ODR-used. Did you expect that implementation behavior, or do you "wonder" and ask questions about it?

If you expected no error, you probably should rethink that whole "ODR-use violation gets a pass by the compiler makes me wondering" thing:

Implementation don't diagnose the lack of definition they don't need to make a working program.

What they need depends on the cleverness of the implementation, as obviously you can make the argument 1. above way more complicated if the non-need of f() in g() is buried in complex code. I intentionally gave a trivial example, one valid at -O0.

[Note: this is assembly for g() at level -O0:

g():
        push    rbp
        mov     rbp, rsp
        mov     QWORD PTR [rbp-8], OFFSET FLAT:_Z1fv
        nop
        pop     rbp
        ret

No dependency on symbol f() as you can see.]

Knowledge depends on optimization level and on the amount of data provided at code generation time (compiling many translation units at the same time might help).

For non virtual functions, the function is either needed (that is named) by a needed thing (another function or a global object initialized with that function address).

For virtual functions, getting knowledge is much more difficult: knowing that an overrider is not going to be called isn't as trivial as with a function that is not an overrider (including a non virtual function), as an overrider can be called via "late binding", when a virtual call is made on a (g)lvalue.

The compiler may or may not be able to determine precisely which virtual functions are never needed, depending on the complexity of the program (the halting program issue shows that they will never to know exactly for all programs), but non static member functions of objects that are never instantiated obviously are never needed.

Related