After I apply Lénárd Szolnoki's proposal to disable implicit conversion of default_delete<D> to default_delete<B>, this particular std::optional test begins to fail:
/llvm-project/libcxx/test/std/utilities/optional/optional.object/optional.object.assign/optional_U.pass.cpp:243:13: error: no viable overloaded '=' opt = std::move(other); ~~~ ^ ~~~~~~~~~~~~~~~~ /llvm-project/libcxx/include/optional:768:41: note: candidate function not viable: no known conversion from 'optional<unique_ptr<D, default_delete<D>>>' to 'optional<unique_ptr<B, default_delete<B>>>' for 1st argument _LIBCPP_INLINE_VISIBILITY optional& operator=(optional&&) = default; ^
I believe this is a true positive. The offending code in this test is circa line 243:
{ optional<std::unique_ptr<B>> opt; optional<std::unique_ptr<D>> other(new D()); opt = std::move(other); assert(static_cast<bool>(opt) == true); assert(static_cast<bool>(other) == true); assert(opt->get() != nullptr); assert(other->get() == nullptr); }
At the closing curly brace, we destroy opt, which deletes opt->get() (a B* which dynamically points to a D object), which has UB because http://eel.is/c++draft/expr.delete#3 — am I right?
Giving B a virtual dtor fixes the UB without affecting anything else about this test.