The following code seems to compile correctly using GCC:

template<class T> struct A;

template<class T>
void g(const A<T>& a) {

template<class T> struct A {
    void f() const {}

int main() {
    A<int> a;

When g() is defined, A<> has only been pre-declared, but inside the function body of g(), its member A::f is accessed.

Is this only allowed by this specific compiler, or is it generally allowed in C++17?
Or would it be needed to change it to something like this?

template<class AType>
void g(const AType& a) {

I’m using this to avoid circular header dependency problems.

This is fine. Don’t forget you can specialize A so there is no guarantee an A<T> even has an f member, even if it is in the primary template.

This is one of the rare cases where the compiler does two passes. First the template itself is checked to make sure it is syntactically correct. Then once the template is instantiated with a type, which in this case happens when you do g(a);, then the specialization that is stamped out is checked for semantic errors, like the member function not existing.