According to the C++20 Standard (Virtual functions [class.virtual])
1 A non-static member function is a virtual function if it is first
declared with the keyword virtual or if it overrides a virtual
member function declared in a base class (see below).
and
2 If a virtual member function vf is declared in a class Base and in a
class Derived, derived directly or indirectly from Base, a member
function vf with the same name, parameter-type-list ([dcl.fct]),
cv-qualification, and ref-qualifier (or absence of same) as Base::vf
is declared, then Derived::vf overrides113 Base::vf.
Pay attention to that even a virtual function can be hidden in some intermediate derived class nevertheless it can be overriden in a subsequent derived class.
That is if for example you will change your classes the following way
class parent
{
public:
    virtual void f() { cout << "parent::f()" << endl; }
};
class child : public parent
{
public:
    void f( int ) { cout << "child::f()" << endl; }
};
class grandchild : public child
{
public:
    void f() { cout << "grandchild::f()" << endl; }
};
where the function f( int ) declared in the class child hides the virtual function f() declared in the class parent nevertheless you can override it in the class grandchild. The program output wll be the same as in the original program.
Here is a demonstration program.
#include <iostream>
using namespace std;
class parent
{
public:
    virtual void f() { cout << "parent::f()" << endl; }
};
class child : public parent
{
public:
    void f( int = 0 ) { cout << "child::f( int )" << endl; }
};
class grandchild : public child
{
public:
    void f() { cout << "grandchild::f()" << endl; }
};
int main()
{
    grandchild grandchild1;
    parent *p = &grandchild1;
    p->f(); 
    child child1;
    p = &child1;
    p->f();
}
The program output is
grandchild::f()
parent::f()
Some remarks. For the class child the final overrider of the virtual function, declared in the class parent, is the function parent::f. For the class grandchild the final overrider is the function grandchild::f.
It is better to use always the specifier override in declarations of virtual functions as for example
class grandchild : public child
{
public:
    void f() override { cout << "grandchild::f()" << endl; }
};
In this case your code will be more clear.