diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h
index 039c70710a28a82cadaadf6b1482247b948b335f..a9ce44bb21478fefab0b4d7a5f23deabe35c6858 100644
--- a/gcc/cp/cp-tree.h
+++ b/gcc/cp/cp-tree.h
@@ -7626,7 +7626,8 @@ enum { nt_opaque = false, nt_transparent = true };
 extern tree alias_template_specialization_p     (const_tree, bool);
 extern tree dependent_alias_template_spec_p     (const_tree, bool);
 extern bool dependent_opaque_alias_p            (const_tree);
-extern tree get_template_parm_object		(tree expr, tree mangle);
+extern tree get_template_parm_object		(tree expr, tree mangle,
+						 bool check_init = true);
 extern tree tparm_object_argument		(tree);
 extern bool explicit_class_specialization_p     (tree);
 extern bool push_tinst_level                    (tree);
diff --git a/gcc/cp/module.cc b/gcc/cp/module.cc
index c3218bd5caf3a53785e2bd4ceb02f80ac6ad9fb8..0a4ceffa3d63377555dfd535a9d8a5b566745a03 100644
--- a/gcc/cp/module.cc
+++ b/gcc/cp/module.cc
@@ -9938,7 +9938,11 @@ trees_in::tree_node (bool is_use)
 	tree name = tree_node ();
 	if (!get_overrun ())
 	  {
-	    res = get_template_parm_object (init, name);
+	    /* We don't want to check the initializer as that may require
+	       name lookup, which could recursively start lazy loading.
+	       Instead we know that INIT is already valid so we can just
+	       apply that directly.  */
+	    res = get_template_parm_object (init, name, /*check_init=*/false);
 	    int tag = insert (res);
 	    dump (dumper::TREE)
 	      && dump ("Created nttp object:%d %N", tag, name);
diff --git a/gcc/cp/pt.cc b/gcc/cp/pt.cc
index 32d164f0fd5b75bfcad2007fd3252ed090e95344..76edc7aad50c8dd6a708f27d0771fd476e3d15e2 100644
--- a/gcc/cp/pt.cc
+++ b/gcc/cp/pt.cc
@@ -7361,10 +7361,11 @@ create_template_parm_object (tree expr, tsubst_flags_t complain)
 static GTY(()) hash_map<tree, tree> *tparm_obj_values;
 
 /* Find or build an nttp object for (already-validated) EXPR with name
-   NAME.  */
+   NAME.  When CHECK_INIT is false we don't need to process the initialiser,
+   it's already been done.  */
 
 tree
-get_template_parm_object (tree expr, tree name)
+get_template_parm_object (tree expr, tree name, bool check_init/*=true*/)
 {
   tree decl = get_global_binding (name);
   if (decl)
@@ -7385,11 +7386,20 @@ get_template_parm_object (tree expr, tree name)
     {
       /* If EXPR contains any PTRMEM_CST, they will get clobbered by
 	 lower_var_init before we're done mangling.  So store the original
-	 value elsewhere.  */
-      tree copy = unshare_constructor (expr);
+	 value elsewhere.  We only need to unshare EXPR if it's not yet
+	 been processed.  */
+      tree copy = check_init ? unshare_constructor (expr) : expr;
       hash_map_safe_put<hm_ggc> (tparm_obj_values, decl, copy);
     }
 
+  if (!check_init)
+    {
+      /* The EXPR is the already processed initializer, set it on the NTTP
+	 object now so that cp_finish_decl doesn't do it again later.  */
+      DECL_INITIAL (decl) = expr;
+      DECL_INITIALIZED_P (decl) = 1;
+    }
+
   pushdecl_top_level_and_finish (decl, expr);
 
   return decl;
diff --git a/gcc/testsuite/g++.dg/modules/tpl-nttp-1_a.C b/gcc/testsuite/g++.dg/modules/tpl-nttp-1_a.C
new file mode 100644
index 0000000000000000000000000000000000000000..46f6ecd0c1db8c1000da187487edc590c2f7eb13
--- /dev/null
+++ b/gcc/testsuite/g++.dg/modules/tpl-nttp-1_a.C
@@ -0,0 +1,8 @@
+// PR c++/116382
+// { dg-additional-options "-fmodules-ts -std=c++20" }
+// { dg-module-cmi m:a }
+
+module m:a;
+template <typename> struct X {};
+template <X<int> nttp> struct index {};
+template struct index<{}>;
diff --git a/gcc/testsuite/g++.dg/modules/tpl-nttp-1_b.C b/gcc/testsuite/g++.dg/modules/tpl-nttp-1_b.C
new file mode 100644
index 0000000000000000000000000000000000000000..a3241e7bc25cadc2a982dae32ea743f9d74412e7
--- /dev/null
+++ b/gcc/testsuite/g++.dg/modules/tpl-nttp-1_b.C
@@ -0,0 +1,6 @@
+// PR c++/116382
+// { dg-additional-options "-fmodules-ts -std=c++20" }
+// { dg-module-cmi m }
+
+export module m;
+import :a;