GCC is right, the program is ill-formed.
What can be done with members of an inline namespace is specified in [namespace.def]/7:
Members of an inline namespace can be used in most respects as though they were members of the enclosing namespace. Specifically, the inline namespace and its enclosing namespace are both added to the set of associated namespaces used in argument-dependent lookup whenever one of them is, and a using-directive that names the inline namespace is implicitly inserted into the enclosing namespace as for an unnamed namespace. Furthermore, each member of the inline namespace can subsequently be partially specialized, explicitly instantiated, or explicitly specialized as though it were a member of the enclosing namespace. Finally, looking up a name in the enclosing namespace via explicit qualification ([namespace.qual]) will include members of the inline namespace brought in by the using-directive even if there are declarations of that name in the enclosing namespace.
So, an inline namespace member can be looked up and even specialized using it's enclosing scope name, but not defined.
To define a member, you still need to fully qualify it. But you can't qualify an unnamed namespace.
To fix it, just give it some name:
namespace ns
{
inline namespace X
{
template<typename T>
void f();
}
}
template<typename T>
void ns::X::f()
{
T t{};
++t;
}
Bonus info:
To understand how unnamed namespaces work, see [namespace.unnamed]/1:
An unnamed-namespace-definition behaves as if it were replaced by
inline(opt) namespace unique { /* empty body */ }
using namespace unique ;
namespace unique { namespace-body }
So an unnamed namespace does actually have a name, it's just hidden from the user. Hence it can never be fully qualified to define stuff in it (which is actually the point).
And [namespace.memdef]/2:
Members of a named namespace can also be defined outside that namespace by explicit qualification ...
Which basically means if you can qualify it, you can define it.