diff --git a/gcc/c-family/c.opt b/gcc/c-family/c.opt
index 979f17a7e3232c0c1eba891ae54f0951a0e9611f..491aa02e1a39b8e4c2f6cad18e534bfb9e144b9f 100644
--- a/gcc/c-family/c.opt
+++ b/gcc/c-family/c.opt
@@ -1420,6 +1420,10 @@ Wtautological-compare
 C ObjC C++ ObjC++ Var(warn_tautological_compare) Warning LangEnabledBy(C ObjC C++ ObjC++,Wall)
 Warn if a comparison always evaluates to true or false.
 
+Wtemplate-body
+C++ ObjC++ Var(warn_template_body) Warning Init(1)
+Diagnose errors when parsing a template.
+
 Wtemplate-id-cdtor
 C++ ObjC++ Var(warn_template_id_cdtor) Warning
 Warn about simple-template-id in a constructor or destructor.
diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h
index b81bc91208fa958c0834148de0628c5a2312d5b1..b1693051231b1e8ea9216abdc8257048c6c3e55c 100644
--- a/gcc/cp/cp-tree.h
+++ b/gcc/cp/cp-tree.h
@@ -7185,6 +7185,13 @@ extern location_t location_of                   (tree);
 extern void qualified_name_lookup_error		(tree, tree, tree,
 						 location_t);
 
+using erroneous_templates_t
+  = hash_map<tree, location_t, simple_hashmap_traits<tree_decl_hash, location_t>>;
+extern erroneous_templates_t *erroneous_templates;
+
+extern bool cp_seen_error ();
+#define seen_error() cp_seen_error ()
+
 /* in except.cc */
 extern void init_terminate_fn			(void);
 extern void init_exception_processing		(void);
diff --git a/gcc/cp/error.cc b/gcc/cp/error.cc
index da853e203dbc3dc3cb6d0ec087d6309fd69dff39..ee3868efaed6269b6df29dd9b01be496b5f5da6b 100644
--- a/gcc/cp/error.cc
+++ b/gcc/cp/error.cc
@@ -165,6 +165,72 @@ class cxx_format_postprocessor : public format_postprocessor
   deferred_printed_type m_type_b;
 };
 
+/* Return the in-scope template that's currently being parsed, or
+   NULL_TREE otherwise.  */
+
+static tree
+get_current_template ()
+{
+  if (scope_chain && in_template_context && !current_instantiation ())
+    if (tree ti = get_template_info (current_scope ()))
+      return TI_TEMPLATE (ti);
+
+  return NULL_TREE;
+}
+
+/* A map from TEMPLATE_DECLs that we've determined to be erroneous
+   at parse time to the location of the first error within.  */
+
+erroneous_templates_t *erroneous_templates;
+
+/* Callback function diagnostic_context::m_adjust_diagnostic_info.
+
+   Errors issued when parsing a template are automatically treated like
+   permerrors associated with the -Wtemplate-body flag and can be
+   downgraded into warnings accordingly, in which case we'll still
+   issue an error if we later need to instantiate the template.  */
+
+static void
+cp_adjust_diagnostic_info (diagnostic_context *context,
+			   diagnostic_info *diagnostic)
+{
+  if (diagnostic->kind == DK_ERROR)
+    if (tree tmpl = get_current_template ())
+      {
+	diagnostic->option_index = OPT_Wtemplate_body;
+
+	if (context->m_permissive)
+	  diagnostic->kind = DK_WARNING;
+
+	bool existed;
+	location_t &error_loc
+	  = hash_map_safe_get_or_insert<false> (erroneous_templates,
+						tmpl, &existed);
+	if (!existed)
+	  /* Remember that this template had a parse-time error so
+	     that we'll ensure a hard error has been issued upon
+	     its instantiation.  */
+	  error_loc = diagnostic->richloc->get_loc ();
+      }
+}
+
+/* A generalization of seen_error which also returns true if we've
+   permissively downgraded an error to a warning inside a template.  */
+
+bool
+cp_seen_error ()
+{
+  if ((seen_error) ())
+    return true;
+
+  if (erroneous_templates)
+    if (tree tmpl = get_current_template ())
+      if (erroneous_templates->get (tmpl))
+	return true;
+
+  return false;
+}
+
 /* CONTEXT->printer is a basic pretty printer that was constructed
    presumably by diagnostic_initialize(), called early in the
    compiler's initialization process (in general_init) Before the FE
@@ -187,6 +253,7 @@ cxx_initialize_diagnostics (diagnostic_context *context)
   diagnostic_starter (context) = cp_diagnostic_starter;
   /* diagnostic_finalizer is already c_diagnostic_finalizer.  */
   diagnostic_format_decoder (context) = cp_printer;
+  context->m_adjust_diagnostic_info = cp_adjust_diagnostic_info;
   pp_format_postprocessor (pp) = new cxx_format_postprocessor ();
 }
 
diff --git a/gcc/cp/module.cc b/gcc/cp/module.cc
index d1607a06757551008dace2f3135bcabb746ce6ef..7130faf26f52f6ddfd9c95fe5aac81a0a8d4a994 100644
--- a/gcc/cp/module.cc
+++ b/gcc/cp/module.cc
@@ -20768,7 +20768,10 @@ finish_module_processing (cpp_reader *reader)
 
       cookie = new module_processing_cookie (cmi_name, tmp_name, fd, e);
 
-      if (errorcount)
+      if (errorcount
+	  /* Don't write the module if it contains an erroneous template.  */
+	  || (erroneous_templates
+	      && !erroneous_templates->is_empty ()))
 	warning_at (state->loc, 0, "not writing module %qs due to errors",
 		    state->get_flatname ());
       else if (cookie->out.begin ())
diff --git a/gcc/cp/pt.cc b/gcc/cp/pt.cc
index 677ed7d128966efcfd8f1d1f3254952f9fb5d17e..9a4ff553b2f307e8ef8e077fdbea6d05143ffa8d 100644
--- a/gcc/cp/pt.cc
+++ b/gcc/cp/pt.cc
@@ -12289,6 +12289,24 @@ perform_instantiation_time_access_checks (tree tmpl, tree targs)
       }
 }
 
+/* If the template T that we're about to instantiate contained errors at
+   parse time that we downgraded into warnings or suppressed, diagnose the
+   error now to render the TU ill-formed (if the TU has not already been
+   deemed ill-formed by an earlier error).  */
+
+static void
+maybe_diagnose_erroneous_template (tree t)
+{
+  if (erroneous_templates && !(seen_error) ())
+    if (location_t *error_loc = erroneous_templates->get (t))
+      {
+	auto_diagnostic_group d;
+	location_t decl_loc = location_of (t);
+	error_at (decl_loc, "instantiating erroneous template");
+	inform (*error_loc, "first error appeared here");
+      }
+}
+
 tree
 instantiate_class_template (tree type)
 {
@@ -12358,6 +12376,8 @@ instantiate_class_template (tree type)
   if (! push_tinst_level (type))
     return type;
 
+  maybe_diagnose_erroneous_template (templ);
+
   int saved_unevaluated_operand = cp_unevaluated_operand;
   int saved_inhibit_evaluation_warnings = c_inhibit_evaluation_warnings;
 
@@ -27251,6 +27271,8 @@ instantiate_decl (tree d, bool defer_ok, bool expl_inst_class_mem_p)
 	}
     }
 
+  maybe_diagnose_erroneous_template (td);
+
   code_pattern = DECL_TEMPLATE_RESULT (td);
 
   /* We should never be trying to instantiate a member of a class
diff --git a/gcc/diagnostic.cc b/gcc/diagnostic.cc
index 3fc81ad47f5698b1d955d90e9ebdde5ff8e6d7be..92bd4f80845338ec4dbb31d74a6041f564cdaa27 100644
--- a/gcc/diagnostic.cc
+++ b/gcc/diagnostic.cc
@@ -219,6 +219,7 @@ diagnostic_context::initialize (int n_opts)
   m_warn_system_headers = false;
   m_max_errors = 0;
   m_internal_error = nullptr;
+  m_adjust_diagnostic_info = nullptr;
   m_text_callbacks.m_begin_diagnostic = default_diagnostic_starter;
   m_text_callbacks.m_start_span = default_diagnostic_start_span_fn;
   m_text_callbacks.m_end_diagnostic = default_diagnostic_finalizer;
@@ -1429,6 +1430,9 @@ diagnostic_context::report_diagnostic (diagnostic_info *diagnostic)
   if (was_warning && m_inhibit_warnings)
     return false;
 
+  if (m_adjust_diagnostic_info)
+    m_adjust_diagnostic_info (this, diagnostic);
+
   if (diagnostic->kind == DK_PEDWARN)
     {
       diagnostic->kind = m_pedantic_errors ? DK_ERROR : DK_WARNING;
diff --git a/gcc/diagnostic.h b/gcc/diagnostic.h
index 83180ded414ddc06ee60296eac0a974085ec2c13..2a9f2751dca28e229ce60d15238c6c04b8c51e6e 100644
--- a/gcc/diagnostic.h
+++ b/gcc/diagnostic.h
@@ -701,6 +701,10 @@ public:
   /* Client hook to report an internal error.  */
   void (*m_internal_error) (diagnostic_context *, const char *, va_list *);
 
+  /* Client hook to adjust properties of the given diagnostic that we're
+     about to issue, such as its kind.  */
+  void (*m_adjust_diagnostic_info)(diagnostic_context *, diagnostic_info *);
+
 private:
   /* Client-supplied callbacks for working with options.  */
   struct {
diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
index 0fe99ca8ef6e8868f60369f6329fe29599d89159..27539a017851d928229574962371089e80620f86 100644
--- a/gcc/doc/invoke.texi
+++ b/gcc/doc/invoke.texi
@@ -270,7 +270,8 @@ in the following sections.
 -Wno-non-template-friend  -Wold-style-cast
 -Woverloaded-virtual  -Wno-pmf-conversions -Wself-move -Wsign-promo
 -Wsized-deallocation  -Wsuggest-final-methods
--Wsuggest-final-types  -Wsuggest-override  -Wno-template-id-cdtor
+-Wsuggest-final-types  -Wsuggest-override  -Wno-template-body
+-Wno-template-id-cdtor
 -Wno-terminate  -Wno-vexing-parse  -Wvirtual-inheritance
 -Wno-virtual-move-assign  -Wvolatile  -Wzero-as-null-pointer-constant}
 
@@ -4634,6 +4635,14 @@ namespaces, and this may be used to enforce that rule.  The warning is
 inactive inside a system header file, such as the STL, so one can still
 use the STL.  One may also use using directives and qualified names.
 
+@opindex Wtemplate-body
+@opindex Wno-template-body
+@item -Wno-template-body @r{(C++ and Objective-C++ only)}
+Disable diagnosing errors when parsing a template, and instead issue an
+error only upon instantiation of the template.  This flag can also be
+used to downgrade such errors into warnings with @option{Wno-error=} or
+@option{-fpermissive}.
+
 @opindex Wtemplate-id-cdtor
 @opindex Wno-template-id-cdtor
 @item -Wno-template-id-cdtor @r{(C++ and Objective-C++ only)}
@@ -6361,6 +6370,7 @@ that have their own flag:
 -Wint-conversion @r{(C and Objective-C only)}
 -Wnarrowing @r{(C++ and Objective-C++ only)}
 -Wreturn-mismatch @r{(C and Objective-C only)}
+-Wtemplate-body @r{(C++ and Objective-C++ only)}
 }
 
 The @option{-fpermissive} option is the default for historic C language
diff --git a/gcc/testsuite/g++.dg/ext/typedef-init.C b/gcc/testsuite/g++.dg/ext/typedef-init.C
index 153303d217b5012baab63b39071df651ca7db25f..47a6642de5108d7d7e3cd9b58d3b45eb56c6d2b3 100644
--- a/gcc/testsuite/g++.dg/ext/typedef-init.C
+++ b/gcc/testsuite/g++.dg/ext/typedef-init.C
@@ -32,5 +32,5 @@ struct S {
 
 template<int> void foo()
 {
-    typedef int i = 0; /* { dg-error "is initialized" } */
+    typedef int i = 0; /* { dg-warning "is initialized" } */
 }
diff --git a/gcc/testsuite/g++.dg/pr84492.C b/gcc/testsuite/g++.dg/pr84492.C
index 1a2922096d19102c54e51b6775986e2a7fab71ae..08f368ff29b60409950b2c1a67556ab58cf9953e 100644
--- a/gcc/testsuite/g++.dg/pr84492.C
+++ b/gcc/testsuite/g++.dg/pr84492.C
@@ -3,7 +3,7 @@
 
 template<int> int foo()
 {
-  return ({ foo; }); // { dg-error "insufficient context" }
+  return ({ foo; }); // { dg-warning "insufficient context" }
 }
 
 int bar()
@@ -35,6 +35,6 @@ class C
   }
   bool g(int)
   {
-    return ({ g; }); // { dg-error "insufficient context" }
+    return ({ g; }); // { dg-warning "insufficient context" }
   }
 };
diff --git a/gcc/testsuite/g++.dg/template/permissive-error1.C b/gcc/testsuite/g++.dg/template/permissive-error1.C
new file mode 100644
index 0000000000000000000000000000000000000000..e4536a8b90e069a1112583f20c9aa625bfff4afb
--- /dev/null
+++ b/gcc/testsuite/g++.dg/template/permissive-error1.C
@@ -0,0 +1,20 @@
+// PR c++/116064
+// { dg-additional-options -fpermissive }
+
+template<class T>
+void f() {
+  const int n = 42;
+  ++n; // { dg-warning "read-only" }
+}
+
+template<class T>
+struct A {
+  void f(typename A::type); // { dg-warning "does not name a type" }
+};
+
+template<class T>
+struct B {
+  void f() {
+    this->g(); // { dg-warning "no member" }
+  }
+};
diff --git a/gcc/testsuite/g++.dg/template/permissive-error1a.C b/gcc/testsuite/g++.dg/template/permissive-error1a.C
new file mode 100644
index 0000000000000000000000000000000000000000..ac47151f0dba9216ca66c64d3ca8a6e474debf19
--- /dev/null
+++ b/gcc/testsuite/g++.dg/template/permissive-error1a.C
@@ -0,0 +1,31 @@
+// PR c++/116064
+// { dg-additional-options -fpermissive }
+// Like permissive-error1.C but verify instantiating the errorneous
+// templates gives an error after all.
+
+template<class T>
+void f() {  // { dg-error "instantiating erroneous template" }
+  const int n = 42;
+  ++n; // { dg-warning "read-only variable 'n' .-Wtemplate-body." }
+       // { dg-message "first error appeared here" "" { target *-*-* } .-1 }
+       // { dg-error "read-only variable 'n'\[\n\r\]" "" { target *-*-* } .-2 }
+}
+
+template<class T>
+struct A {
+  void f(typename A::type); // { dg-warning "does not name a type" }
+};
+
+template<class T>
+struct B {
+  void f() {
+    this->g(); // { dg-warning "no member" }
+  }
+};
+
+int main() {
+  f<int>(); // { dg-message "required from here" }
+  A<int> a;
+  B<int> b;
+  b.f();
+}
diff --git a/gcc/testsuite/g++.dg/template/permissive-error1b.C b/gcc/testsuite/g++.dg/template/permissive-error1b.C
new file mode 100644
index 0000000000000000000000000000000000000000..a67b73746256f4d8e0e18ad2e31c0556072a4e16
--- /dev/null
+++ b/gcc/testsuite/g++.dg/template/permissive-error1b.C
@@ -0,0 +1,30 @@
+// PR c++/116064
+// { dg-additional-options -Wno-template-body }
+// Like permissive-error1a.C but verify -Wno-template-body suppresses
+// diagnostics.
+
+template<class T>
+void f() {  // { dg-error "instantiating erroneous template" }
+  const int n = 42;
+  ++n; // { dg-message "first error appeared here" "" { target *-*-* } }
+       // { dg-error "read-only variable 'n'\[\n\r\]" "" { target *-*-* } .-1 }
+}
+
+template<class T>
+struct A {
+  void f(typename A::type);
+};
+
+template<class T>
+struct B {
+  void f() {
+    this->g();
+  }
+};
+
+int main() {
+  f<int>(); // { dg-message "required from here" }
+  A<int> a;
+  B<int> b;
+  b.f();
+}
diff --git a/gcc/testsuite/g++.dg/template/permissive-error1c.C b/gcc/testsuite/g++.dg/template/permissive-error1c.C
new file mode 100644
index 0000000000000000000000000000000000000000..fd5f26655e5400f02f89f4450330363b6870a2a4
--- /dev/null
+++ b/gcc/testsuite/g++.dg/template/permissive-error1c.C
@@ -0,0 +1,31 @@
+// PR c++/116064
+// { dg-additional-options -Wno-error=template-body }
+// Like permissive-error1a.C but verify the diagnostics can also
+// be downgraded via Wno-error=template-body.
+
+template<class T>
+void f() {  // { dg-error "instantiating erroneous template" }
+  const int n = 42;
+  ++n; // { dg-warning "read-only variable 'n' .-Wtemplate-body." }
+       // { dg-message "first error appeared here" "" { target *-*-* } .-1 }
+       // { dg-error "read-only variable 'n'\[\n\r\]" "" { target *-*-* } .-2 }
+}
+
+template<class T>
+struct A {
+  void f(typename A::type); // { dg-warning "does not name a type" }
+};
+
+template<class T>
+struct B {
+  void f() {
+    this->g(); // { dg-warning "no member" }
+  }
+};
+
+int main() {
+  f<int>(); // { dg-message "required from here" }
+  A<int> a;
+  B<int> b;
+  b.f();
+}
diff --git a/gcc/testsuite/g++.old-deja/g++.pt/crash51.C b/gcc/testsuite/g++.old-deja/g++.pt/crash51.C
index a3fbc17f163b42e9a44398e461107100df5d39b0..c5cbde521ade4d0857b0492a5dd5e9e7ebdd3392 100644
--- a/gcc/testsuite/g++.old-deja/g++.pt/crash51.C
+++ b/gcc/testsuite/g++.old-deja/g++.pt/crash51.C
@@ -1,5 +1,4 @@
 // { dg-do assemble  }
-// { dg-options "-fpermissive -w" }
 // Origin: Mark Mitchell <mark@codesourcery.com>
 
 char foo[26];