E' qualcosa al limite dell'UB. All'atto pratico alcuni compilatori (non so se tutti) permettono di invocare un metodo attraverso un puntatore non valido PURCHE' tale metodo non acceda a variabili membro (si presume che tale comportamento abbia a che fare con ralune ottimizzazioni).
Se vuoi un UB basta che la funzione f() di Foo acceda a un dato membro (ad es. un int data).
codice:
struct Foo
{
    ~Foo() { cout << "~Foo()\n"; }
    void f() const { data = 0; cout << "Foo:f()\n"; } // BOOM!
    int data;
};