diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h
index ec976928f5f..d5efca834f5 100644
--- a/gcc/cp/cp-tree.h
+++ b/gcc/cp/cp-tree.h
@@ -1922,6 +1922,11 @@ struct GTY(()) cp_omp_begin_assumes_data {
   bool attr_syntax;
 };
 
+struct GTY(()) cp_omp_declare_variant_attr {
+  bool attr_syntax;
+  tree selector;
+};
+
 /* Global state.  */
 
 struct GTY(()) saved_scope {
@@ -1972,6 +1977,7 @@ struct GTY(()) saved_scope {
   hash_map<tree, tree> *GTY((skip)) x_local_specializations;
   vec<cp_omp_declare_target_attr, va_gc> *omp_declare_target_attribute;
   vec<cp_omp_begin_assumes_data, va_gc> *omp_begin_assumes;
+  vec<cp_omp_declare_variant_attr, va_gc> *omp_declare_variant_attribute;
 
   struct saved_scope *prev;
 };
diff --git a/gcc/cp/decl.cc b/gcc/cp/decl.cc
index 84abc17ade0..63cd90de0f7 100644
--- a/gcc/cp/decl.cc
+++ b/gcc/cp/decl.cc
@@ -8545,6 +8545,21 @@ omp_declare_variant_finalize_one (tree decl, tree attr)
   if (idk == CP_ID_KIND_QUALIFIED)
     variant = finish_call_expr (variant, &args, /*disallow_virtual=*/true,
 				koenig_p, tf_warning_or_error);
+  else if (idk == CP_ID_KIND_NONE
+	   && TREE_CODE (variant) == FUNCTION_DECL
+	   && DECL_IOBJ_MEMBER_FUNCTION_P (variant)
+	   && CLASS_TYPE_P (DECL_CONTEXT (decl)))
+    {
+      tree saved_ccp = current_class_ptr;
+      tree saved_ccr = current_class_ref;
+      current_class_ptr = NULL_TREE;
+      current_class_ref = NULL_TREE;
+      inject_this_parameter (DECL_CONTEXT (decl), TYPE_UNQUALIFIED);
+      variant = finish_call_expr (variant, &args, /*disallow_virtual=*/false,
+				  koenig_p, tf_warning_or_error);
+      current_class_ptr = saved_ccp;
+      current_class_ref = saved_ccr;
+    }
   else
     variant = finish_call_expr (variant, &args, /*disallow_virtual=*/false,
 				koenig_p, tf_warning_or_error);
diff --git a/gcc/cp/parser.cc b/gcc/cp/parser.cc
index 9d0970b4d83..35a7b32789d 100644
--- a/gcc/cp/parser.cc
+++ b/gcc/cp/parser.cc
@@ -4524,6 +4524,54 @@ cp_parser_require_pragma_eol (cp_parser *parser, cp_token *pragma_tok)
     }
 }
 
+/* Skip tokens up to and including "#pragma omp end declare variant".
+   Properly handle nested "#pragma omp begin declare variant" pragmas.  */
+static void
+cp_parser_skip_to_pragma_omp_end_declare_variant (cp_parser *parser)
+{
+  for (int depth = 0; depth >= 0; )
+    {
+      cp_token *token = cp_lexer_peek_token (parser->lexer);
+
+      switch (token->type)
+	{
+	case CPP_PRAGMA_EOL:
+	  if (!parser->lexer->in_pragma)
+	    break;
+	  /* FALLTHRU */
+	case CPP_EOF:
+	  /* If we've run out of tokens, stop.  */
+	  return;
+
+	case CPP_PRAGMA:
+	  if ((cp_parser_pragma_kind (token) == PRAGMA_OMP_BEGIN
+	       || cp_parser_pragma_kind (token) == PRAGMA_OMP_END)
+	      && cp_lexer_nth_token_is (parser->lexer, 2, CPP_NAME)
+	      && cp_lexer_nth_token_is (parser->lexer, 3, CPP_NAME))
+	    {
+	      tree id1 = cp_lexer_peek_nth_token (parser->lexer, 2)->u.value;
+	      tree id2 = cp_lexer_peek_nth_token (parser->lexer, 3)->u.value;
+	      if (strcmp (IDENTIFIER_POINTER (id1), "declare") == 0
+		  && strcmp (IDENTIFIER_POINTER (id2), "variant") == 0)
+		{
+		  if (cp_parser_pragma_kind (token) == PRAGMA_OMP_BEGIN)
+		    depth++;
+		  else
+		    depth--;
+		}
+	    }
+	  cp_parser_skip_to_pragma_eol (parser, token);
+	  continue;
+
+	default:
+	  break;
+	}
+
+      /* Consume the token.  */
+      cp_lexer_consume_token (parser->lexer);
+    }
+}
+
 /* This is a simple wrapper around make_typename_type. When the id is
    an unresolved identifier node, we can provide a superior diagnostic
    using cp_parser_diagnose_invalid_type_name.  */
@@ -23770,6 +23818,226 @@ cp_parser_maybe_adjust_declarator_for_dguide (cp_parser *parser,
     }
 }
 
+/* Helper function for OpenMP "begin declare variant" directives.
+   Function definitions inside the construct need to have their names
+   mangled according to the context selector CTX.  The DECLARATOR is
+   modified in place to point to a new identifier; the original name of
+   the function is returned.  */
+static tree
+omp_start_variant_function (cp_declarator *declarator, tree ctx)
+{
+  cp_declarator *id = get_id_declarator (declarator);
+  tree name = id->u.id.unqualified_name;
+  tree scope = id->u.id.qualifying_scope;
+  enum special_function_kind sfk = id->u.id.sfk;
+
+  /* There seems to be no reasonable interpretation of what the behavior
+     should be if the name is qualified.  You cannot add the variant function
+     to a class or namespace from outside of that scope.  */
+  if (scope)
+    {
+      sorry_at (id->id_loc,
+		"cannot handle qualified name for variant function");
+      return NULL_TREE;
+    }
+
+  /* Catch disallowed constructors and destructors now.  We can't mangle
+     destructor names (which are not IDENTIFIER_NODEs) in any case.  */
+  if (sfk == sfk_constructor)
+    {
+      error_at (id->id_loc,
+		"declare variant directives are not allowed on constructors");
+      return NULL_TREE;
+    }
+  if (sfk == sfk_destructor)
+    {
+      error_at (id->id_loc,
+		"declare variant directives are not allowed on destructors");
+      return NULL_TREE;
+    }
+  if (TREE_CODE (name) != IDENTIFIER_NODE)
+    {
+      sorry_at (id->id_loc,
+		"cannot handle %s identifier name",
+		get_tree_code_name (TREE_CODE (name)));
+      return NULL_TREE;
+    }
+
+  /* Mangle the name in the declarator.  */
+  id->u.id.unqualified_name
+    = omp_mangle_variant_name (name, ctx, JOIN_STR);
+
+  return name;
+}
+
+/* Helper function for OpenMP "begin declare variant" directives.  Now
+   that we have a DECL for the variant function, and BASE_NAME for the
+   base function, look up the decl for BASE_NAME in the same scope as
+   DECL, add an "omp declare variant base" attribute pointing at CTX
+   to the base decl, and an "omp declare variant variant" attribute to
+   the variant DECL.  */
+static void
+omp_finish_variant_function (cp_parser *parser, tree decl, tree base_name,
+			     tree ctx)
+{
+  tree match = NULL_TREE;
+  bool is_template = false;
+  tree decl_context = CP_DECL_CONTEXT (decl);
+
+  /* First find the base_decl.  */
+  tree base_decl = cp_parser_lookup_name_simple (parser, base_name,
+						 DECL_SOURCE_LOCATION (decl));
+
+  if (base_decl == error_mark_node)
+    base_decl = NULL_TREE;
+  if (!base_decl)
+    {
+      error_at (DECL_SOURCE_LOCATION (decl),
+		"no previous declaration of base function in this scope");
+      return;
+    }
+
+  /* Find the right overloaded function.  */
+  if (TREE_CODE (base_decl) == OVERLOAD)
+    {
+      for (ovl_iterator iter (base_decl); iter; ++iter)
+	{
+	  tree bb = *iter;
+	  if (decls_match (decl, bb))
+	    {
+	      match = bb;
+	      break;
+	    }
+	  else if (TREE_CODE (bb) == TEMPLATE_DECL
+		   && TREE_CODE (decl) == FUNCTION_DECL
+		   && DECL_TEMPLATE_INFO (decl))
+	    {
+	      tree decl_template = DECL_TI_TEMPLATE (decl);
+	      if (decl_template
+		  && PRIMARY_TEMPLATE_P (decl_template)
+		  && decls_match (bb, decl_template))
+		{
+		  /* We want to put the attributes on the function rather
+		     than on the TEMPLATE_DECL that points to it.  */
+		  match = DECL_TEMPLATE_RESULT (bb);
+		  is_template = true;
+		  break;
+		}
+	    }
+	}
+      }
+  else if (decls_match (decl, base_decl))
+    match = base_decl;
+  else if (TREE_CODE (base_decl) == TEMPLATE_DECL)
+    /* Per comment in cp-tree.h, TEMPLATE_DECLs are always wrapped in an
+       OVERLOAD, so we should never see them here.  */
+    gcc_unreachable ();
+  else if (TREE_CODE (base_decl) == TREE_LIST)
+    {
+      error_at (DECL_SOURCE_LOCATION (decl), "base function is ambiguous");
+      return;
+    }
+  else if (TREE_CODE (base_decl) == SCOPE_REF)
+    {
+      /* This shows up in some cases involving templates; it's apparently a
+	 placeholder for names that can't be matched to a declaration
+	 until template instantiation.  */
+      sorry_at (DECL_SOURCE_LOCATION (decl),
+		"base function cannot be resolved");
+      return;
+    }
+
+  if (!match)
+    {
+      error_at (DECL_SOURCE_LOCATION (decl),
+		"variant function definition does not match previous "
+		"declaration of %qE", base_decl);
+      return;
+    }
+  else if (CP_DECL_CONTEXT (match) != decl_context)
+    {
+      /* Reject inherited or using decls.  */
+      error_at (DECL_SOURCE_LOCATION (decl),
+		"variant function must be in the same scope as the "
+		"base function %qE", match);
+      return;
+    }
+  else if (DECL_VIRTUAL_P (decl) || DECL_VIRTUAL_P (match))
+    {
+      error_at (DECL_SOURCE_LOCATION (decl),
+		"declare variant directives are not allowed on "
+		"virtual functions");
+      return;
+    }
+  else if (DECL_DEFAULTED_FN (decl) || DECL_DEFAULTED_FN (match))
+    {
+      error_at (DECL_SOURCE_LOCATION (decl),
+		"declare variant directives are not allowed on "
+		"defaulted functions");
+      return;
+    }
+  else if (DECL_DELETED_FN (decl) || DECL_DELETED_FN (match))
+    {
+      error_at (DECL_SOURCE_LOCATION (decl),
+		"declare variant directives are not allowed on "
+		"deleted functions");
+      return;
+    }
+  else if (DECL_IMMEDIATE_FUNCTION_P (decl)
+	   || DECL_IMMEDIATE_FUNCTION_P (match))
+    {
+      error_at (DECL_SOURCE_LOCATION (decl),
+		"declare variant directives are not allowed on "
+		"immediate functions");
+      return;
+    }
+
+  /* Inside a template, make the "omp declare variant base" attribute
+     point to the name of DECL rather than DECL itself.  During template
+     instantiation, omp_declare_variant_finalize_one will handle this
+     using the same logic as for the non-delimited form of "declare variant",
+     causing template instantiation as needed.  For the non-template case,
+     there is nothing that will trigger omp_declare_variant_finalize_one;
+     so we create the final form of the attribute here, which points
+     directly to DECL rather than its name.  */
+  tree decl_or_name = decl;
+  cp_id_kind idk = CP_ID_KIND_NONE;
+  if (processing_template_decl && is_template)
+    {
+      decl_or_name = DECL_NAME (decl);
+      idk = CP_ID_KIND_TEMPLATE_ID;
+    }
+
+  omp_check_for_duplicate_variant (DECL_SOURCE_LOCATION (decl),
+				   match, ctx);
+  tree construct
+    = omp_get_context_selector_list (ctx, OMP_TRAIT_SET_CONSTRUCT);
+  omp_mark_declare_variant (DECL_SOURCE_LOCATION (decl), decl, construct);
+
+  tree attrs = DECL_ATTRIBUTES (match);
+  tree match_loc_node
+    = maybe_wrap_with_location (integer_zero_node,
+				DECL_SOURCE_LOCATION (match));
+  tree loc_node = tree_cons (match_loc_node,
+			     build_int_cst (integer_type_node, idk),
+			     build_tree_list (match_loc_node,
+					      integer_zero_node));
+  attrs = tree_cons (get_identifier ("omp declare variant base"),
+		     tree_cons (decl_or_name, ctx, loc_node), attrs);
+  if (processing_template_decl)
+    ATTR_IS_DEPENDENT (attrs) = 1;
+  DECL_ATTRIBUTES (match) = attrs;
+
+  /* Variant functions are essentially anonymous and cannot be
+     referenced by name, so make them have internal linkage.  Note
+     that class methods in C++ normally have external linkage with
+     weak/comdat semantics; this prevents that.  */
+  TREE_PUBLIC (decl) = 0;
+  DECL_COMDAT (decl) = 0;
+  DECL_INTERFACE_KNOWN (decl) = 1;
+  DECL_NOT_REALLY_EXTERN (decl) = 1;
+}
+
 /* Declarators [gram.dcl.decl] */
 
 /* Parse an init-declarator.
@@ -23986,6 +24254,27 @@ cp_parser_init_declarator (cp_parser* parser,
 	  /* This is a function-definition.  */
 	  *function_definition_p = true;
 
+	  /* If we're in an OpenMP "begin declare variant" block, the
+	     name in the declarator refers to the base function.  We need
+	     to save that and modify the declarator to have the mangled
+	     name for the variant function instead.  */
+	  tree dv_base = NULL_TREE;
+	  tree dv_ctx = NULL_TREE;
+	  vec<cp_omp_declare_variant_attr, va_gc> *dv_state
+	    = scope_chain->omp_declare_variant_attribute;
+
+	  if (!vec_safe_is_empty (dv_state))
+	    {
+	      cp_omp_declare_variant_attr a = dv_state->last ();
+	      dv_ctx = copy_list (a.selector);
+	      dv_base = omp_start_variant_function (declarator, dv_ctx);
+	      if (dv_base == NULL_TREE)
+		{
+		  cp_parser_skip_to_end_of_statement (parser);
+		  return error_mark_node;
+		}
+	    }
+
 	  /* Parse the function definition.  */
 	  if (member_p)
 	    decl = cp_parser_save_member_function_body (parser,
@@ -24004,6 +24293,11 @@ cp_parser_init_declarator (cp_parser* parser,
 		= func_brace_location;
 	    }
 
+	  /* If this function was in a "begin declare variant" block,
+	     store the pointer back to the base function and fix up
+	     the attributes for the middle end.  */
+	  if (dv_base && decl != error_mark_node)
+	    omp_finish_variant_function (parser, decl, dv_base, dv_ctx);
 	  return decl;
 	}
     }
@@ -24081,6 +24375,27 @@ cp_parser_init_declarator (cp_parser* parser,
 	    is_initialized = SD_DEFAULTED;
 	  else if (t2->keyword == RID_DELETE)
 	    is_initialized = SD_DELETED;
+	  if (!vec_safe_is_empty (scope_chain->omp_declare_variant_attribute))
+	    {
+	      /* We're in a "begin declare variant" construct.  The parser
+		 doesn't go through the normal function definition path for
+		 these and hence doesn't invoke omp_finish_variant_function
+		 where these errors would otherwise be caught.  */
+	      if (is_initialized == SD_DEFAULTED)
+		{
+		  error_at (declarator->init_loc,
+			    "declare variant directives are not allowed on "
+			    "defaulted functions");
+		  return error_mark_node;
+		}
+	      else if (is_initialized == SD_DELETED)
+		{
+		  error_at (declarator->init_loc,
+			    "declare variant directives are not allowed on "
+			    "deleted functions");
+		  return error_mark_node;
+		}
+	    }
 	}
     }
   else
@@ -27536,6 +27851,10 @@ cp_parser_class_specifier (cp_parser* parser)
   tree saved_ccr = current_class_ref;
   current_class_ptr = NULL_TREE;
   current_class_ref = NULL_TREE;
+  /* Set up for deferred lookup of "omp begin declare variant" base functions
+     in the class.  */
+  tree save_unregistered_variants = parser->omp_unregistered_variants;
+  parser->omp_unregistered_variants = NULL_TREE;
 
   /* Start the class.  */
   if (nested_name_specifier_p)
@@ -27557,6 +27876,19 @@ cp_parser_class_specifier (cp_parser* parser)
     /* Parse the member-specification.  */
     cp_parser_member_specification_opt (parser);
 
+  /* Register any "begin declare variant" functions in this class, since
+     references to the base function can only be resolved after the
+     entire class is seen.  */
+  for (tree bdv = parser->omp_unregistered_variants; bdv;
+       bdv = TREE_CHAIN (bdv))
+    {
+      tree dv_base = TREE_PURPOSE (TREE_PURPOSE (bdv));
+      tree dv_ctx = TREE_VALUE (TREE_PURPOSE (bdv));
+      tree dv_decl = TREE_VALUE (bdv);
+      omp_finish_variant_function (parser, dv_decl, dv_base, dv_ctx);
+    }
+  parser->omp_unregistered_variants = save_unregistered_variants;
+
   /* Look for the trailing `}'.  */
   closing_brace = braces.require_close (parser);
   /* Look for trailing attributes to apply to this class.  */
@@ -29216,6 +29548,28 @@ cp_parser_member_declaration (cp_parser* parser)
 		  if (initializer && initializer_token_start)
 		    error_at (initializer_token_start->location,
 			      "pure-specifier on function-definition");
+
+		  /* If we're in an OpenMP "begin declare variant" block,
+		     the name in the declarator refers to the base function.
+		     We need to save that and modify the declarator to have
+		     the mangled name for the variant function instead.  */
+		  tree dv_base = NULL_TREE;
+		  tree dv_ctx = NULL_TREE;
+		  vec<cp_omp_declare_variant_attr, va_gc> *dv_state
+		    = scope_chain->omp_declare_variant_attribute;
+		  if (!vec_safe_is_empty (dv_state))
+		    {
+		      cp_omp_declare_variant_attr a = dv_state->last ();
+		      dv_ctx = copy_list (a.selector);
+		      dv_base = omp_start_variant_function (declarator,
+							    dv_ctx);
+		      if (dv_base == NULL_TREE)
+			{
+			  cp_parser_skip_to_end_of_statement (parser);
+			  goto out;
+			}
+		    }
+
 		  decl = cp_parser_save_member_function_body (parser,
 							      &decl_specifiers,
 							      declarator,
@@ -29226,6 +29580,19 @@ cp_parser_member_declaration (cp_parser* parser)
 		  /* If the member was not a friend, declare it here.  */
 		  if (!friend_p)
 		    finish_member_declaration (decl);
+
+		  /* If this function was in a "begin declare variant"
+		     block, record the information we need to find the
+		     base function and fix it up later.  At this point in
+		     parsing, we may not have seen the base function yet
+		     so we defer looking it up and registering the variant
+		     until the class is complete.  */
+		  if (dv_base && decl != error_mark_node)
+		    parser->omp_unregistered_variants
+		      = tree_cons (tree_cons (dv_base, dv_ctx, NULL_TREE),
+				   decl,
+				   parser->omp_unregistered_variants);
+
 		  /* Peek at the next token.  */
 		  token = cp_lexer_peek_token (parser->lexer);
 		  /* If the next token is a semicolon, consume it.  */
@@ -50439,6 +50806,20 @@ cp_finish_omp_declare_variant (cp_parser *parser, cp_token *pragma_tok,
 	    goto fail;
 	  ctx = omp_check_context_selector (match_loc, ctx,
 					    OMP_CTX_DECLARE_VARIANT);
+
+	  /* The OpenMP spec says the merging rules for enclosing
+	     "begin declare variant" contexts apply to "declare variant
+	     directives" -- the term it uses to refer to both directive
+	     forms.  */
+	  if (ctx != error_mark_node
+	      && !vec_safe_is_empty (scope_chain->omp_declare_variant_attribute))
+	    {
+	      cp_omp_declare_variant_attr a
+		= scope_chain->omp_declare_variant_attribute->last ();
+	      tree outer_ctx = a.selector;
+	      ctx = omp_merge_context_selectors (match_loc, outer_ctx, ctx,
+						 OMP_CTX_DECLARE_VARIANT);
+	    }
 	  if (ctx != error_mark_node && variant != error_mark_node)
 	    {
 	      tree match_loc_node
@@ -51096,7 +51477,9 @@ cp_parser_omp_declare_target (cp_parser *parser, cp_token *pragma_tok)
 /* OpenMP 5.1
    # pragma omp begin assumes clauses[optseq] new-line
 
-   # pragma omp begin declare target clauses[optseq] new-line  */
+   # pragma omp begin declare target clauses[optseq] new-line
+
+   # pragma omp begin declare variant (match context-selector) new-line  */
 
 #define OMP_BEGIN_DECLARE_TARGET_CLAUSE_MASK			\
 	( (OMP_CLAUSE_MASK_1 << PRAGMA_OMP_CLAUSE_DEVICE_TYPE)	\
@@ -51142,9 +51525,73 @@ cp_parser_omp_begin (cp_parser *parser, cp_token *pragma_tok)
 	    = { in_omp_attribute_pragma, device_type, indirect };
 	  vec_safe_push (scope_chain->omp_declare_target_attribute, a);
 	}
+      else if (strcmp (p, "variant") == 0)
+	{
+	  cp_lexer_consume_token (parser->lexer);
+	  const char *clause = "";
+	  matching_parens parens;
+	  location_t match_loc = cp_lexer_peek_token (parser->lexer)->location;
+	  if (cp_lexer_next_token_is (parser->lexer, CPP_NAME))
+	    {
+	      tree id = cp_lexer_peek_token (parser->lexer)->u.value;
+	      clause = IDENTIFIER_POINTER (id);
+	    }
+	  if (strcmp (clause, "match") != 0)
+	    {
+	      cp_parser_error (parser, "expected %<match%>");
+	      cp_parser_skip_to_pragma_eol (parser, pragma_tok);
+	      return;
+	    }
+
+	  cp_lexer_consume_token (parser->lexer);
+
+	  if (!parens.require_open (parser))
+	    {
+	      cp_parser_skip_to_pragma_eol (parser, pragma_tok);
+	      return;
+	    }
+
+	  tree ctx = cp_parser_omp_context_selector_specification (parser,
+								   true);
+	  if (ctx != error_mark_node)
+	    ctx = omp_check_context_selector (match_loc, ctx,
+					      OMP_CTX_BEGIN_DECLARE_VARIANT);
+
+	  if (ctx != error_mark_node
+	      && !vec_safe_is_empty (scope_chain->omp_declare_variant_attribute))
+	    {
+	      cp_omp_declare_variant_attr a
+		= scope_chain->omp_declare_variant_attribute->last ();
+	      tree outer_ctx = a.selector;
+	      ctx = omp_merge_context_selectors (match_loc, outer_ctx, ctx,
+						 OMP_CTX_BEGIN_DECLARE_VARIANT);
+	    }
+
+	  if (ctx == error_mark_node
+	      || !omp_context_selector_matches (ctx, NULL_TREE, false, true))
+	    {
+	      /* The context is either invalid or cannot possibly match.
+		 In the latter case the spec says all code in the begin/end
+		 sequence will be elided.  In the former case we'll get bogus
+		 errors from trying to parse it without a valid context to
+		 use for name-mangling, so elide that too.  */
+	      cp_parser_skip_to_pragma_eol (parser, pragma_tok);
+	      cp_parser_skip_to_pragma_omp_end_declare_variant (parser);
+	      return;
+	    }
+	  else
+	    {
+	      cp_omp_declare_variant_attr a
+		= { parser->lexer->in_omp_attribute_pragma, ctx };
+	      vec_safe_push (scope_chain->omp_declare_variant_attribute, a);
+	    }
+
+	  parens.require_close (parser);
+	  cp_parser_skip_to_pragma_eol (parser, pragma_tok);
+	}
       else
 	{
-	  cp_parser_error (parser, "expected %<target%>");
+	  cp_parser_error (parser, "expected %<target%> or %<variant%>");
 	  cp_parser_skip_to_pragma_eol (parser, pragma_tok);
 	}
     }
@@ -51157,7 +51604,8 @@ cp_parser_omp_begin (cp_parser *parser, cp_token *pragma_tok)
     }
   else
     {
-      cp_parser_error (parser, "expected %<declare target%> or %<assumes%>");
+      cp_parser_error (parser, "expected %<declare target%>, "
+		       "%<declare variant%>, or %<assumes%>");
       cp_parser_skip_to_pragma_eol (parser, pragma_tok);
     }
 }
@@ -51166,7 +51614,8 @@ cp_parser_omp_begin (cp_parser *parser, cp_token *pragma_tok)
    # pragma omp end declare target new-line
 
    OpenMP 5.1:
-   # pragma omp end assumes new-line  */
+   # pragma omp end assumes new-line
+   # pragma omp end declare variant new-line  */
 
 static void
 cp_parser_omp_end (cp_parser *parser, cp_token *pragma_tok)
@@ -51188,41 +51637,70 @@ cp_parser_omp_end (cp_parser *parser, cp_token *pragma_tok)
 	  p = IDENTIFIER_POINTER (id);
 	}
       if (strcmp (p, "target") == 0)
-	cp_lexer_consume_token (parser->lexer);
+	{
+	  cp_lexer_consume_token (parser->lexer);
+	  cp_parser_require_pragma_eol (parser, pragma_tok);
+	  if (!vec_safe_length (scope_chain->omp_declare_target_attribute))
+	    error_at (pragma_tok->location,
+		      "%<#pragma omp end declare target%> without "
+		      "corresponding %<#pragma omp declare target%> or "
+		      "%<#pragma omp begin declare target%>");
+	  else
+	    {
+	      cp_omp_declare_target_attr
+		a = scope_chain->omp_declare_target_attribute->pop ();
+	      if (a.attr_syntax != in_omp_attribute_pragma)
+		{
+		  if (a.attr_syntax)
+		    error_at (pragma_tok->location,
+			      "%qs in attribute syntax terminated "
+			      "with %qs in pragma syntax",
+			      a.device_type >= 0 ? "begin declare target"
+						 : "declare target",
+			      "end declare target");
+		  else
+		    error_at (pragma_tok->location,
+			      "%qs in pragma syntax terminated "
+			      "with %qs in attribute syntax",
+			      a.device_type >= 0 ? "begin declare target"
+						 : "declare target",
+			      "end declare target");
+		}
+	    }
+	}
+      else if (strcmp (p, "variant") == 0)
+	{
+	  cp_lexer_consume_token (parser->lexer);
+	  cp_parser_require_pragma_eol (parser, pragma_tok);
+	  if (!vec_safe_length (scope_chain->omp_declare_variant_attribute))
+	    error_at (pragma_tok->location,
+		      "%<#pragma omp end declare variant%> without "
+		      "corresponding %<#pragma omp begin declare variant%>");
+	  else
+	    {
+	      cp_omp_declare_variant_attr
+		a = scope_chain->omp_declare_variant_attribute->pop ();
+	      if (a.attr_syntax != in_omp_attribute_pragma)
+		{
+		  if (a.attr_syntax)
+		    error_at (pragma_tok->location,
+			      "%<begin declare variant%> in attribute syntax "
+			      "terminated with %<end declare variant%> in "
+			      "pragma syntax");
+		  else
+		    error_at (pragma_tok->location,
+			      "%<begin declare variant%> in pragma syntax "
+			      "terminated with %<end declare variant%> in "
+			      "attribute syntax");
+		}
+	    }
+	}
       else
 	{
 	  cp_parser_error (parser, "expected %<target%>");
 	  cp_parser_skip_to_pragma_eol (parser, pragma_tok);
 	  return;
 	}
-      cp_parser_require_pragma_eol (parser, pragma_tok);
-      if (!vec_safe_length (scope_chain->omp_declare_target_attribute))
-	error_at (pragma_tok->location,
-		  "%<#pragma omp end declare target%> without corresponding "
-		  "%<#pragma omp declare target%> or "
-		  "%<#pragma omp begin declare target%>");
-      else
-	{
-	  cp_omp_declare_target_attr
-	    a = scope_chain->omp_declare_target_attribute->pop ();
-	  if (a.attr_syntax != in_omp_attribute_pragma)
-	    {
-	      if (a.attr_syntax)
-		error_at (pragma_tok->location,
-			  "%qs in attribute syntax terminated "
-			  "with %qs in pragma syntax",
-			  a.device_type >= 0 ? "begin declare target"
-					     : "declare target",
-			  "end declare target");
-	      else
-		error_at (pragma_tok->location,
-			  "%qs in pragma syntax terminated "
-			  "with %qs in attribute syntax",
-			  a.device_type >= 0 ? "begin declare target"
-					     : "declare target",
-			  "end declare target");
-	    }
-	}
     }
   else if (strcmp (p, "assumes") == 0)
     {
diff --git a/gcc/cp/parser.h b/gcc/cp/parser.h
index f9ed80123c4..d998f7e9af7 100644
--- a/gcc/cp/parser.h
+++ b/gcc/cp/parser.h
@@ -456,6 +456,11 @@ struct GTY(()) cp_parser {
      outside that file.  */
   struct omp_metadirective_parse_data * GTY((skip))
     omp_metadirective_state;
+
+  /* TREE_LIST of "omp begin declare variant" functions when the base
+     function has not been seen "elsewhere" (per the OpenMP spec) yet;
+     used only when these functions are seen in a class definition.  */
+  tree omp_unregistered_variants;
 };
 
 /* In parser.cc  */
diff --git a/gcc/cp/semantics.cc b/gcc/cp/semantics.cc
index a2ee3a34caa..7a38dfb76e3 100644
--- a/gcc/cp/semantics.cc
+++ b/gcc/cp/semantics.cc
@@ -3963,6 +3963,13 @@ finish_translation_unit (void)
 	       "#pragma omp end declare target");
       vec_safe_truncate (scope_chain->omp_declare_target_attribute, 0);
     }
+  if (vec_safe_length (scope_chain->omp_declare_variant_attribute))
+    {
+      if (!errorcount)
+	error ("%<omp begin declare variant%> without corresponding "
+	       "%<omp end declare variant%>");
+      vec_safe_truncate (scope_chain->omp_declare_variant_attribute, 0);
+    }
   if (vec_safe_length (scope_chain->omp_begin_assumes))
     {
       if (!errorcount)
diff --git a/gcc/testsuite/g++.dg/gomp/delim-declare-variant-1.C b/gcc/testsuite/g++.dg/gomp/delim-declare-variant-1.C
new file mode 100644
index 00000000000..dfeb7c42d08
--- /dev/null
+++ b/gcc/testsuite/g++.dg/gomp/delim-declare-variant-1.C
@@ -0,0 +1,39 @@
+/* { dg-do compile } */
+/* { dg-additional-options "-foffload=disable -fdump-tree-gimple" } */
+
+/* Check that variants within a "begin declare variant" directive 
+   are attached to the correct overloaded function.  */
+
+int f (int x) { return x; }
+
+#pragma omp begin declare variant match (implementation={vendor("gnu")})
+int f (int x) { return -1; }
+#pragma omp end declare variant
+
+int f (int x, int y) { return x * y; }
+
+#pragma omp begin declare variant match (construct={target})
+int f (int x, int y) { return -2; }
+#pragma omp end declare variant
+
+int f (int x, int y, int z) { return x * y * z; }
+
+#pragma omp begin declare variant match (device={kind("host")})
+int f (int x, int y, int z) { return -3; }
+#pragma omp end declare variant
+
+int main (void)
+{
+  if (f (10) != -1) __builtin_abort ();
+  if (f (10, 20) != 200) __builtin_abort ();   /* no match on this one */
+  if (f (10, 20, 30) != -3) __builtin_abort ();
+}
+
+/* { dg-final { scan-tree-dump "f\\.ompvariant. \\(10\\)" "gimple" } } */
+/* { dg-final { scan-tree-dump "f \\(10, 20\\)" "gimple" } } */
+/* { dg-final { scan-tree-dump "f\\.ompvariant. \\(10, 20, 30\\)" "gimple" } } */
+
+
+
+
+
diff --git a/gcc/testsuite/g++.dg/gomp/delim-declare-variant-2.C b/gcc/testsuite/g++.dg/gomp/delim-declare-variant-2.C
new file mode 100644
index 00000000000..1784e14dfc3
--- /dev/null
+++ b/gcc/testsuite/g++.dg/gomp/delim-declare-variant-2.C
@@ -0,0 +1,53 @@
+/* { dg-do compile } */
+/* { dg-additional-options "-fdump-tree-gimple" } */
+
+/* Check that "omp begin declare variant" works on methods in a 
+   class declaration.  */
+
+class test1 {
+
+ private:
+  int n;
+  static int m;
+
+ public:
+
+  #pragma omp begin declare variant match (implementation={vendor("gnu")})
+  int get_n (void) { return n * 2; }
+  static int get_m (void) { return m * 2; }
+  #pragma omp end declare variant
+
+  #pragma omp begin declare variant match (construct={target})
+  int get_n (void) { return this->n * 2; }
+  #pragma omp end declare variant
+
+  /* The base methods are deliberately declared after the variants in order
+     to check that the lookup can still find them.  */
+  void set_n (int x) { n = x; }
+  int get_n (void) { return n; }
+  
+  static void set_m (int x) { m = x; }
+  static int get_m (void) { return m; }
+};
+
+int test1::m;
+
+int main (void)
+{
+  test1 t1;
+  t1.set_n (10);
+  if (t1.get_n () != 20) __builtin_abort ();
+  test1::set_m (1);
+  if (test1::get_m () != 2) __builtin_abort ();
+}
+
+/* { dg-final { scan-tree-dump "test1::get_n\\.ompvariant. \\(&t1\\)" "gimple" } } */
+/* { dg-final { scan-tree-dump "test1::get_m\\.ompvariant. \\(\\)" "gimple" } } */
+
+/* The variants must have internal linkage, not .globl or .weak.  */
+/* { dg-final { scan-assembler-not "\\.globl\[ \t\]*_?_ZN5test117get_n\\.ompvariant" } } */
+/* { dg-final { scan-assembler-not "\\.globl\[ \t\]*_?_ZN5test117get_m\\.ompvariant" } } */
+/* { dg-final { scan-assembler-not "\\.weak\[ \t\]*_?_ZN5test117get_n\\.ompvariant" } } */
+/* { dg-final { scan-assembler-not "\\.weak\[ \t\]*_?_ZN5test117get_m\\.ompvariant" } } */
+
+
diff --git a/gcc/testsuite/g++.dg/gomp/delim-declare-variant-3.C b/gcc/testsuite/g++.dg/gomp/delim-declare-variant-3.C
new file mode 100644
index 00000000000..ccbb01ce794
--- /dev/null
+++ b/gcc/testsuite/g++.dg/gomp/delim-declare-variant-3.C
@@ -0,0 +1,37 @@
+/* { dg-do compile } */
+
+/* Check that "omp begin declare variant" for class methods outside of the
+   class declaration gives a sorry.  C++ generally does not allow injection
+   of additional methods into a class outside of its declaration so it is
+   not clear what this is supposed to do.  */
+
+class test1 {
+
+ private:
+  int n;
+  static int m;
+
+ public:
+
+  void set_n (int x) { n = x; }
+  int get_n (void) { return n; }
+  
+  static void set_m (int x) { m = x; }
+  static int get_m (void) { return m; }
+
+};
+
+#pragma omp begin declare variant match (implementation={vendor("gnu")})
+int test1::get_n (void) { return n * 2; }  /* { dg-message "sorry, unimplemented: cannot handle qualified name for variant function" } */
+static int test1::get_m (void) { return m * 2; }  /* { dg-message "sorry, unimplemented: cannot handle qualified name for variant function" } */
+#pragma omp end declare variant
+
+int main (void)
+{
+  test1 t1;
+  t1.set_n (10);
+  if (t1.get_n () != 20) __builtin_abort ();
+  test1::set_m (1);
+  if (test1::get_m () != 2) __builtin_abort ();
+}
+
diff --git a/gcc/testsuite/g++.dg/gomp/delim-declare-variant-4.C b/gcc/testsuite/g++.dg/gomp/delim-declare-variant-4.C
new file mode 100644
index 00000000000..567cf9c4f8c
--- /dev/null
+++ b/gcc/testsuite/g++.dg/gomp/delim-declare-variant-4.C
@@ -0,0 +1,57 @@
+/* { dg-do compile } */
+/* { dg-additional-options "-fdump-tree-gimple" } */
+
+/* Like c-c++-common/delim-declare-variant-1.c, but with namespaces.  */
+
+namespace n1 {
+
+int foo (int a)
+{
+  return a;
+}
+
+int bar (int x)
+{
+  return x;
+}
+
+#pragma omp begin declare variant match (construct={target})
+int foo (int a)
+{
+  return a + 1;
+}
+
+int bar (int x)
+{
+  return x * 2;
+}
+#pragma omp end declare variant
+
+/* Because of the high score value, this variant for "bar" should always be
+   selected even when the one above also matches.  */
+#pragma omp begin declare variant match (implementation={vendor(score(10000):"gnu")})
+int bar (int x)
+{
+  return x * 4;
+}
+#pragma omp end declare variant
+
+} /* namespace n1 */
+
+int main (void)
+{
+  if (n1::foo (42) != 42) __builtin_abort ();
+  if (n1::bar (3) != 12) __builtin_abort ();
+#pragma omp target
+  {
+    if (n1::foo (42) != 43) __builtin_abort ();
+    if (n1::bar (3) != 12) __builtin_abort ();
+  }
+}
+
+/* { dg-final { scan-tree-dump-times "omp declare variant base \\(foo.ompvariant." 1 "gimple" } } */
+/* { dg-final { scan-tree-dump-times "omp declare variant base \\(bar.ompvariant." 2 "gimple" } } */
+/* { dg-final { scan-tree-dump-times "foo \\(42\\)" 1 "gimple" } } */
+/* { dg-final { scan-tree-dump-times "foo\\.ompvariant. \\(42\\)" 1 "gimple" } } */
+/* { dg-final { scan-tree-dump-times "bar \\(3\\)" 0 "gimple" } } */
+/* { dg-final { scan-tree-dump-times "bar\\.ompvariant. \\(3\\)" 2 "gimple" } } */
diff --git a/gcc/testsuite/g++.dg/gomp/delim-declare-variant-40.C b/gcc/testsuite/g++.dg/gomp/delim-declare-variant-40.C
new file mode 100644
index 00000000000..4f35d209d25
--- /dev/null
+++ b/gcc/testsuite/g++.dg/gomp/delim-declare-variant-40.C
@@ -0,0 +1,51 @@
+// { dg-do compile }
+
+// Check that variants for a template function are instantiated correctly.
+// FIXME:  Fails due to PR118530.  
+
+template<typename T>
+void f_default_param (T = 42) {}
+#pragma omp begin declare variant match (implementation={vendor("gnu")})
+template<typename T>
+void f_default_param (T = 42) {}
+#pragma omp end declare variant
+
+template<typename T>
+void f_no_param () {}  // { dg-bogus "no matching function for call" "PR118530" { xfail *-*-* } }
+#pragma omp begin declare variant match (implementation={vendor("gnu")})
+template<typename T>
+void f_no_param () {}
+#pragma omp end declare variant
+
+void instantiate_f()
+{
+  f_default_param<int>();
+  f_no_param<int>();
+}
+
+template<int>
+void nttp () {}  // { dg-bogus "no matching function for call" "PR118530" { xfail *-*-* } }
+#pragma omp begin declare variant match (implementation={vendor("gnu")})
+template<int>
+void nttp () {}
+#pragma omp end declare variant
+
+void instantiate_nttp()
+{
+  nttp<42>();
+}
+
+template<typename>
+struct S {};
+
+template<template<typename> class Templ>
+void templ_templ () {}  // { dg-bogus "no matching function for call" "PR118530" { xfail *-*-* } }
+#pragma omp begin declare variant match (implementation={vendor("gnu")})
+template<template<typename> class Templ>
+void templ_templ () {}
+#pragma omp end declare variant
+
+void instantiate_templ_templ()
+{
+  templ_templ<S>();
+}
diff --git a/gcc/testsuite/g++.dg/gomp/delim-declare-variant-41.C b/gcc/testsuite/g++.dg/gomp/delim-declare-variant-41.C
new file mode 100644
index 00000000000..38e41e7847b
--- /dev/null
+++ b/gcc/testsuite/g++.dg/gomp/delim-declare-variant-41.C
@@ -0,0 +1,31 @@
+/* { dg-do compile { target c++11 } } */
+
+/* This test case fails in omp_finish_variant_function because base_decl
+   is a SCOPE_REF and it cannot be resolved to an actual base function decl
+   to hang the variant attribute on.  */
+
+template<typename T, typename U>
+  class is_same {
+  static constexpr bool value = false;
+};
+
+template<typename T>
+class is_same<T, T> {
+  static constexpr bool value = true;
+};
+
+template<typename T>
+void fn (T&&) { }
+
+#pragma omp begin declare variant match(implementation={vendor("gnu")})
+template<typename T>
+void fn(T&&) {  // { dg-bogus "base function cannot be resolved" "" { xfail *-*-* } }
+  static_assert(is_same<T, int>::value);
+}
+#pragma omp end declare variant
+
+int main()
+{
+  int lvalue = 42;
+  fn(0);
+}
diff --git a/gcc/testsuite/g++.dg/gomp/delim-declare-variant-5.C b/gcc/testsuite/g++.dg/gomp/delim-declare-variant-5.C
new file mode 100644
index 00000000000..e8db369a67d
--- /dev/null
+++ b/gcc/testsuite/g++.dg/gomp/delim-declare-variant-5.C
@@ -0,0 +1,53 @@
+/* { dg-do compile } */
+
+/* Check that "omp begin declare variant" for a namespace function outside of
+   the namespace gives an error.  C++ generally does not allow injection of
+   additional function into a namespace outside of its scope so this is just a
+   generic error.  */
+
+namespace n1 {
+
+int foo (int a)
+{
+  return a;
+}
+
+int bar (int x)
+{
+  return x;
+}
+
+} /* namespace n1 */
+
+
+#pragma omp begin declare variant match (construct={target})
+int n1::foo (int a)  /* { dg-message "sorry, unimplemented: cannot handle qualified name for variant function" } */
+{
+  return a + 1;
+}
+
+int n1::bar (int x)  /* { dg-message "sorry, unimplemented: cannot handle qualified name for variant function" } */
+{
+  return x * 2;
+}
+#pragma omp end declare variant
+
+/* Because of the high score value, this variant for "bar" should always be
+   selected even when the one above also matches.  */
+#pragma omp begin declare variant match (implementation={vendor(score(10000):"gnu")})
+int n1::bar (int x)  /* { dg-message "sorry, unimplemented: cannot handle qualified name for variant function" } */
+{
+  return x * 4;
+}
+#pragma omp end declare variant
+
+int main (void)
+{
+  if (n1::foo (42) != 42) __builtin_abort ();
+  if (n1::bar (3) != 12) __builtin_abort ();
+#pragma omp target
+  {
+    if (n1::foo (42) != 43) __builtin_abort ();
+    if (n1::bar (3) != 12) __builtin_abort ();
+  }
+}
diff --git a/gcc/testsuite/g++.dg/gomp/delim-declare-variant-50.C b/gcc/testsuite/g++.dg/gomp/delim-declare-variant-50.C
new file mode 100644
index 00000000000..a958b52d01e
--- /dev/null
+++ b/gcc/testsuite/g++.dg/gomp/delim-declare-variant-50.C
@@ -0,0 +1,99 @@
+/* { dg-do compile } */
+
+/* Test for restrictions on declare variant functions on virtual functions,
+   constructors, and destructors.  */
+
+struct S0
+{
+  virtual void f_virtual_before0 () {}
+  #pragma omp begin declare variant match (implementation={vendor("gnu")})
+  virtual void f_virtual_before0 () {}  // { dg-error "declare variant directives are not allowed on virtual functions" }
+  #pragma omp end declare variant
+
+  virtual void f_virtual_before1 () {}
+  #pragma omp begin declare variant match (implementation={vendor("gnu")})
+  void f_virtual_before1 () {}  // { dg-error "declare variant directives are not allowed on virtual functions" }
+  #pragma omp end declare variant
+
+  void f_virtual_before2 () {}
+  #pragma omp begin declare variant match (implementation={vendor("gnu")})
+  virtual void f_virtual_before2 () {}  // { dg-error "declare variant directives are not allowed on virtual functions" }
+  #pragma omp end declare variant
+
+  void f_virtual_before3 () {}
+  // code elision, no error
+  #pragma omp begin declare variant match (implementation={vendor("cray")})
+  virtual void f_virtual_before3 () {}
+  #pragma omp end declare variant
+
+  #pragma omp begin declare variant match (implementation={vendor("gnu")})
+  virtual void f_virtual_after0 () {}  // { dg-error "declare variant directives are not allowed on virtual functions" }
+  #pragma omp end declare variant
+  virtual void f_virtual_after0 () {}
+
+  #pragma omp begin declare variant match (implementation={vendor("gnu")})
+  void f_virtual_after1 () {}  // { dg-error "declare variant directives are not allowed on virtual functions" }
+  #pragma omp end declare variant
+  virtual void f_virtual_after1 () {}
+
+  #pragma omp begin declare variant match (implementation={vendor("gnu")})
+  virtual void f_virtual_after2 () {}  // { dg-error "declare variant directives are not allowed on virtual functions" }
+  #pragma omp end declare variant
+  void f_virtual_after2 () {}
+};
+
+struct S_before {
+  S_before() {}
+  #pragma omp begin declare variant match (implementation={vendor("gnu")})
+  S_before() {}  // { dg-error "declare variant directives are not allowed on constructors" }
+  #pragma omp end declare variant
+
+  S_before(int) {}
+  #pragma omp begin declare variant match (implementation={vendor("gnu")})
+  S_before(int) {}  // { dg-error "declare variant directives are not allowed on constructors" }
+  #pragma omp end declare variant
+
+  S_before(double) {}
+  // code elision, no error
+  #pragma omp begin declare variant match (implementation={vendor("cray")})
+  S_before(double) {}
+  #pragma omp end declare variant
+  
+  template<typename T>
+  S_before(T) {}
+  #pragma omp begin declare variant match (implementation={vendor("gnu")})
+  template<typename T>
+  S_before(T) {}  // { dg-error "declare variant directives are not allowed on constructors" }
+  #pragma omp end declare variant
+
+  ~S_before() {}
+  #pragma omp begin declare variant match (implementation={vendor("gnu")})
+  ~S_before() {}  // { dg-error "declare variant directives are not allowed on destructors" }
+  #pragma omp end declare variant
+};
+
+struct S_after {
+  #pragma omp begin declare variant match (implementation={vendor("gnu")})
+  S_after() {}  // { dg-error "declare variant directives are not allowed on constructors" }
+  #pragma omp end declare variant
+  S_after() {}
+
+  #pragma omp begin declare variant match (implementation={vendor("gnu")})
+  S_after(int) {}  // { dg-error "declare variant directives are not allowed on constructors" }
+  #pragma omp end declare variant
+  S_after(int) {}
+
+  #pragma omp begin declare variant match (implementation={vendor("gnu")})
+  template<typename T>
+  S_after(T) {}  // { dg-error "declare variant directives are not allowed on constructors" }
+  #pragma omp end declare variant
+  template<typename T>
+  S_after(T) {}
+
+  // code elision, no error
+  #pragma omp begin declare variant match (implementation={vendor("cray")})
+  ~S_after() {}
+  #pragma omp end declare variant
+  ~S_after() {}
+};
+
diff --git a/gcc/testsuite/g++.dg/gomp/delim-declare-variant-51.C b/gcc/testsuite/g++.dg/gomp/delim-declare-variant-51.C
new file mode 100644
index 00000000000..9e65309b0df
--- /dev/null
+++ b/gcc/testsuite/g++.dg/gomp/delim-declare-variant-51.C
@@ -0,0 +1,181 @@
+/* { dg-do compile { target c++11 } } */
+
+/* Test delimited declare variant on constexpr, deleted, and defaulted
+   functions.  */
+/* C++11 */
+
+/* TODO: add templates cases for constexpr/delete free functions */
+
+/* Do we warn for the mismatch?
+   TBH we probably warn whenever a variant function is constexpr in general.
+   I can't imagine that we are going to support constant evaluation of a
+   variant function, realistically the only choice is to always use the base
+   function if a constant-expression is required.  */
+constexpr int freefn_mismatched_constexpr_before0 () { return 0; }
+#pragma omp begin declare variant match (implementation={vendor("gnu")})
+int freefn_mismatched_constexpr_before0 () { return 1; }
+#pragma omp end declare variant
+
+int freefn_mismatched_constexpr_before1 () { return 0; }
+#pragma omp begin declare variant match (implementation={vendor("gnu")})
+constexpr int freefn_mismatched_constexpr_before1 () { return 1; }
+#pragma omp end declare variant
+
+#pragma omp begin declare variant match (implementation={vendor("gnu")})
+constexpr int freefn_mismatched_constexpr_after0 () { return 1; }  // { dg-error "no previous declaration of base function" }
+#pragma omp end declare variant
+int freefn_mismatched_constexpr_after0 () { return 0; }
+
+#pragma omp begin declare variant match (implementation={vendor("gnu")})
+int freefn_mismatched_constexpr_after1 () { return 1; }  // { dg-error "no previous declaration of base function" }
+#pragma omp end declare variant
+constexpr int freefn_mismatched_constexpr_after1 () { return 0; }
+
+
+
+void freefn_deleted_before () = delete;
+#pragma omp begin declare variant match (implementation={vendor("gnu")})
+void freefn_deleted_before () {}  // { dg-error "declare variant directives are not allowed on deleted functions" }
+#pragma omp end declare variant
+
+#pragma omp begin declare variant match (implementation={vendor("gnu")})
+void freefn_deleted_after () {}  // { dg-error "no previous declaration of base function" }
+#pragma omp end declare variant
+void freefn_deleted_after () = delete;
+
+/* TECHNICALLY allowed by the spec, but obviously conflicts with the intention.  */
+void freefn_variant_deleted_base_before () {}
+#pragma omp begin declare variant match (implementation={vendor("gnu")})
+void freefn_variant_deleted_base_before () = delete;  // { dg-error "declare variant directives are not allowed on deleted functions" }
+#pragma omp end declare variant
+
+#pragma omp begin declare variant match (implementation={vendor("gnu")})
+void freefn_variant_deleted_base_after () = delete;  // { dg-error "declare variant directives are not allowed on deleted functions" }
+#pragma omp end declare variant
+void freefn_variant_deleted_base_after () {};
+
+
+/* For now, obviously error, not sure if we error on just the base or on
+   both though.
+   In the future, I think if the base and all variants are deleted, we can
+   treat a call to the function as deleted before we determine a variant.  */
+void freefn_both_deleted_base_before () = delete;
+#pragma omp begin declare variant match (implementation={vendor("gnu")})
+void freefn_both_deleted_base_before () = delete;  // { dg-error "declare variant directives are not allowed on deleted functions" }
+#pragma omp end declare variant
+
+#pragma omp begin declare variant match (implementation={vendor("gnu")})
+void freefn_both_deleted_base_after () = delete;  // { dg-error "declare variant directives are not allowed on deleted functions" }
+#pragma omp end declare variant
+void freefn_both_deleted_base_after () = delete;
+
+
+
+
+struct S0
+{
+  void f_deleted_before () = delete;
+  #pragma omp begin declare variant match (implementation={vendor("gnu")})
+  void f_deleted_before () {}  // { dg-error "declare variant directives are not allowed on deleted functions" }
+  #pragma omp end declare variant
+
+  #pragma omp begin declare variant match (implementation={vendor("gnu")})
+  void f_deleted_after () {}  // { dg-error "declare variant directives are not allowed on deleted functions" }
+  #pragma omp end declare variant
+  void f_deleted_after () = delete;
+};
+
+
+/* These should error for constructor/destructor, not default.  */
+struct S_default_before {
+  S_default_before() = default;
+  #pragma omp begin declare variant match (implementation={vendor("gnu")})
+  S_default_before() {}  // { dg-error "declare variant directives are not allowed on constructors" }
+  #pragma omp end declare variant
+
+  S_default_before(S_default_before const&) = default;
+  #pragma omp begin declare variant match (implementation={vendor("gnu")})
+  S_default_before(S_default_before const&) {}  // { dg-error "declare variant directives are not allowed on constructors" }
+  #pragma omp end declare variant
+
+  S_default_before(S_default_before&&) = default;
+  #pragma omp begin declare variant match (implementation={vendor("gnu")})
+  S_default_before(S_default_before&&) {}  // { dg-error "declare variant directives are not allowed on constructors" }
+  #pragma omp end declare variant
+
+  ~S_default_before() = default;
+  #pragma omp begin declare variant match (implementation={vendor("gnu")})
+  ~S_default_before() {}  // { dg-error "declare variant directives are not allowed on destructors" }
+  #pragma omp end declare variant
+};
+
+struct S_default_after {
+  #pragma omp begin declare variant match (implementation={vendor("gnu")})
+  S_default_after() {}  // { dg-error "declare variant directives are not allowed on constructors" }
+  #pragma omp end declare variant
+  S_default_after() = default;
+
+  #pragma omp begin declare variant match (implementation={vendor("gnu")})
+  S_default_after(S_default_after const&) {}  // { dg-error "declare variant directives are not allowed on constructors" }
+  #pragma omp end declare variant
+  S_default_after(S_default_after const&) = default;
+
+  #pragma omp begin declare variant match (implementation={vendor("gnu")})
+  S_default_after(S_default_after&&) {}  // { dg-error "declare variant directives are not allowed on constructors" }
+  #pragma omp end declare variant
+  S_default_after(S_default_after&&) = default;
+
+  #pragma omp begin declare variant match (implementation={vendor("gnu")})
+  ~S_default_after() {}  // { dg-error "declare variant directives are not allowed on destructors" }
+  #pragma omp end declare variant
+  ~S_default_after() = default;
+};
+
+/* These should error for default/delete.  */
+struct S_default_assignment_before {
+  S_default_assignment_before& operator=(S_default_assignment_before const&) = default;
+  #pragma omp begin declare variant match (implementation={vendor("gnu")})
+  S_default_assignment_before& operator=(S_default_assignment_before const&) { return *this; }  // { dg-error "declare variant directives are not allowed on defaulted functions" }
+  #pragma omp end declare variant
+
+  S_default_assignment_before& operator=(S_default_assignment_before&&) = default;
+  #pragma omp begin declare variant match (implementation={vendor("gnu")})
+  S_default_assignment_before& operator=(S_default_assignment_before&&) { return *this; }  // { dg-error "declare variant directives are not allowed on defaulted functions" }
+  #pragma omp end declare variant
+};
+
+struct S_default_assignment_after {
+  #pragma omp begin declare variant match (implementation={vendor("gnu")})
+  S_default_assignment_after& operator=(S_default_assignment_after const&) { return *this; }  // { dg-error "declare variant directives are not allowed on defaulted functions" }
+  #pragma omp end declare variant
+  S_default_assignment_after& operator=(S_default_assignment_after const&) = default;
+
+  #pragma omp begin declare variant match (implementation={vendor("gnu")})
+  S_default_assignment_after& operator=(S_default_assignment_after&&) { return *this; }  // { dg-error "declare variant directives are not allowed on defaulted functions" }
+  #pragma omp end declare variant
+  S_default_assignment_after& operator=(S_default_assignment_after&&) = default;
+};
+
+struct S_deleted_assignment_before {
+  S_deleted_assignment_before& operator=(S_deleted_assignment_before const&) = delete;
+  #pragma omp begin declare variant match (implementation={vendor("gnu")})
+  S_deleted_assignment_before& operator=(S_deleted_assignment_before const&) { return *this; }  // { dg-error "declare variant directives are not allowed on deleted functions" }
+  #pragma omp end declare variant
+
+  S_deleted_assignment_before& operator=(S_deleted_assignment_before&&) = delete;
+  #pragma omp begin declare variant match (implementation={vendor("gnu")})
+  S_deleted_assignment_before& operator=(S_deleted_assignment_before&&) { return *this; }  // { dg-error "declare variant directives are not allowed on deleted functions" }
+  #pragma omp end declare variant
+};
+
+struct S_deleted_assignment_after {
+  #pragma omp begin declare variant match (implementation={vendor("gnu")})
+  S_deleted_assignment_after& operator=(S_deleted_assignment_after const&) { return *this; }  // { dg-error "declare variant directives are not allowed on deleted functions" }
+  #pragma omp end declare variant
+  S_deleted_assignment_after& operator=(S_deleted_assignment_after const&) = delete;
+
+  #pragma omp begin declare variant match (implementation={vendor("gnu")})
+  S_deleted_assignment_after& operator=(S_deleted_assignment_after&&) { return *this; }  // { dg-error "declare variant directives are not allowed on deleted functions" }
+  #pragma omp end declare variant
+  S_deleted_assignment_after& operator=(S_deleted_assignment_after&&) = delete;
+};
diff --git a/gcc/testsuite/g++.dg/gomp/delim-declare-variant-52.C b/gcc/testsuite/g++.dg/gomp/delim-declare-variant-52.C
new file mode 100644
index 00000000000..4f4a005c6c7
--- /dev/null
+++ b/gcc/testsuite/g++.dg/gomp/delim-declare-variant-52.C
@@ -0,0 +1,24 @@
+/* { dg-do compile { target c++20 } } */
+
+/* The procedure that a declare variant directive determined to be a function
+   variant may not be an immediate function + Declare variant directives may
+   not be specified for immediate functions. */
+consteval void freefn_consteval_before0 () {}
+#pragma omp begin declare variant match (implementation={vendor("gnu")})
+consteval void freefn_consteval_before0 () {}  // { dg-error "declare variant directives are not allowed on immediate functions" }
+#pragma omp end declare variant
+
+/* Declare variant directives may not be specified for immediate functions.  */
+consteval void freefn_consteval_before1 () {}
+#pragma omp begin declare variant match (implementation={vendor("gnu")})
+void freefn_consteval_before1 () {}  // { dg-error "declare variant directives are not allowed on immediate functions" }
+#pragma omp end declare variant
+
+/* The procedure that a declare variant directive determined to be a function
+   variant may not be an immediate function.  */
+void freefn_consteval_before2 () {}
+#pragma omp begin declare variant match (implementation={vendor("gnu")})
+consteval void freefn_consteval_before2 () {}  // { dg-error "declare variant directives are not allowed on immediate functions" }
+#pragma omp end declare variant
+
+
diff --git a/gcc/testsuite/g++.dg/gomp/delim-declare-variant-6.C b/gcc/testsuite/g++.dg/gomp/delim-declare-variant-6.C
new file mode 100644
index 00000000000..30dee4cc607
--- /dev/null
+++ b/gcc/testsuite/g++.dg/gomp/delim-declare-variant-6.C
@@ -0,0 +1,72 @@
+/* { dg-do compile } */
+/* { dg-additional-options "-fdump-tree-gimple" } */
+
+/* Check "begin declare variant" on template functions.  */
+
+template <typename T>
+T foo (T a)
+{
+  return a;
+}
+
+template <typename T>
+T bar (T x)
+{
+  return x;
+}
+
+#pragma omp begin declare variant match (construct={target})
+template <typename T1>
+T1 foo (T1 a)
+{
+  return a + 1;
+}
+
+template <typename T1>
+T1 bar (T1 x)
+{
+  return x * 2;
+}
+#pragma omp end declare variant
+
+/* Because of the high score value, this variant for "bar" should always be
+   selected even when the one above also matches.  */
+#pragma omp begin declare variant match (implementation={vendor(score(10000):"gnu")})
+template <typename T2>
+T2 bar (T2 x)
+{
+  return x * 4;
+}
+#pragma omp end declare variant
+
+int main (void)
+{
+  if (foo<int> (42) != 42) __builtin_abort ();
+  if (bar<int> (3) != 12) __builtin_abort ();
+#pragma omp target
+  {
+    if (foo<int> (42) != 43) __builtin_abort ();
+    if (bar<int> (3) != 12) __builtin_abort ();
+  }
+}
+
+/* Make sure all the template functions are instantiated.  */
+/* { dg-final { scan-tree-dump "int foo.ompvariant.<int> \\(.*\\)" "gimple" } }
+/* { dg-final { scan-tree-dump "int foo<int> \\(.*\\)" "gimple" } }
+/* { dg-final { scan-tree-dump "int bar.ompvariant.<int> \\(.*\\)" "gimple" } }
+
+/* Make sure the calls are resolved correctly.  */
+/* { dg-final { scan-tree-dump-times "foo<int> \\(42\\)" 1 "gimple" } } */
+/* { dg-final { scan-tree-dump-times "foo\\.ompvariant.<int> \\(42\\)" 1 "gimple" } } */
+/* { dg-final { scan-tree-dump-times "bar<int> \\(3\\)" 0 "gimple" } } */
+/* { dg-final { scan-tree-dump-times "bar\\.ompvariant.<int> \\(3\\)" 2 "gimple" } } */
+
+/* The variants must have internal linkage, not .globl or .weak.  */
+/* { dg-final { scan-assembler-not "\\.globl\[ \t\]*_?_Z15foo.ompvariant" } } */
+/* { dg-final { scan-assembler-not "\\.globl\[ \t\]*_?_Z15bar.ompvariant" } } */
+/* { dg-final { scan-assembler-not "\\.weak\[ \t\]*_?_Z15foo.ompvariant" } } */
+/* { dg-final { scan-assembler-not "\\.weak\[ \t\]*_?_Z15bar.ompvariant" } } */
+
+
+
+
diff --git a/gcc/testsuite/g++.dg/gomp/delim-declare-variant-7.C b/gcc/testsuite/g++.dg/gomp/delim-declare-variant-7.C
new file mode 100644
index 00000000000..b24e6c0cb3b
--- /dev/null
+++ b/gcc/testsuite/g++.dg/gomp/delim-declare-variant-7.C
@@ -0,0 +1,57 @@
+/* { dg-do compile } */
+/* { dg-additional-options "-fdump-tree-gimple" } */
+
+/* Check that "omp begin declare variant" works on methods in a template
+   class declaration.  */
+
+template <typename T>
+class test1 {
+
+ private:
+  T n;
+  static T m;
+
+ public:
+
+  void set_n (T x) { n = x; }
+  T get_n (void) { return n; }
+  
+  static void set_m (T x) { m = x; }
+  static T get_m (void) { return m; }
+
+  #pragma omp begin declare variant match (implementation={vendor("gnu")})
+  T get_n (void) { return n * 2; }
+  static T get_m (void) { return m * 2; }
+  #pragma omp end declare variant
+
+  #pragma omp begin declare variant match (construct={target})
+  T get_n (void) { return this->n * 2; }
+  #pragma omp end declare variant
+};
+
+template <typename T>
+T test1<T>::m;
+
+int main (void)
+{
+  test1<int> t1;
+  t1.set_n (10);
+  if (t1.get_n () != 20) __builtin_abort ();
+  test1<int>::set_m (1);
+  if (test1<int>::get_m () != 2) __builtin_abort ();
+}
+
+/* Make sure the "declare variant" replacement happens.  */
+/* { dg-final { scan-tree-dump "test1<int>::get_n\\.ompvariant. \\(&t1\\)" "gimple" } } */
+/* { dg-final { scan-tree-dump "test1<int>::get_m\\.ompvariant. \\(\\)" "gimple" } } */
+
+/* Make sure the variant methods are instantiated.  */
+/* { dg-final { scan-tree-dump "int test1<int>::get_n\\.ompvariant. \\(.*\\)" "gimple" } }  */
+/* { dg-final { scan-tree-dump "int test1<int>::get_m\\.ompvariant. \\(.*\\)" "gimple" } }  */
+
+/* The variants must have internal linkage, not .globl or .weak.  */
+/* { dg-final { scan-assembler-not "\\.globl\[ \t\]*_?_ZN5test1IiE17get_n.ompvariant" } } */
+/* { dg-final { scan-assembler-not "\\.globl\[ \t\]*_?_ZN5test1IiE17get_m.ompvariant" } } */
+/* { dg-final { scan-assembler-not "\\.weak\[ \t\]*_?_ZN5test1IiE17get_n.ompvariant" } } */
+/* { dg-final { scan-assembler-not "\\.weak\[ \t\]*_?_ZN5test1IiE17get_m.ompvariant" } } */
+
diff --git a/gcc/testsuite/g++.dg/gomp/delim-declare-variant-70.C b/gcc/testsuite/g++.dg/gomp/delim-declare-variant-70.C
new file mode 100644
index 00000000000..ed1e1ae2ddd
--- /dev/null
+++ b/gcc/testsuite/g++.dg/gomp/delim-declare-variant-70.C
@@ -0,0 +1,206 @@
+/* { dg-do compile { target c++11 } } */
+
+/* Check that the substituted type in variant is the same as the one in the
+   base.  */
+
+template<typename T, typename U>
+struct is_same {
+  static constexpr bool value = false;
+};
+
+template<typename T>
+struct is_same<T, T> {
+  static constexpr bool value = true;
+};
+
+/* Using static_assert directly in a variant triggers the SCOPE_REF bug noted
+   in delim-declare-variant-41.C.  We'll avoid that by outsourcing the checks
+   to this function.  PR118791 is a different bug that affects also the
+   non-delimited form of "declare variant".  */
+template<typename T, typename U>
+void fail_if_not_same() {
+  static_assert(is_same<T, U>::value);  // { dg-bogus "static assertion failed" "PR118791" { xfail *-*-* } }
+}
+
+/* Sanity checks are included in the base function just to be absolutely
+   certain there were no mistakes made in the tests.  They should match the
+   cases in the variant function exactly.  */
+
+template<typename T>
+void fwdref_passed_lvalue_int (T&& p) {
+  static_assert(is_same<T, int&>::value);
+  static_assert(is_same<decltype(p), int&>::value);
+  static_assert(is_same<decltype((p)), int&>::value);
+}
+#pragma omp begin declare variant match (implementation={vendor("gnu")})
+template<typename T>
+void fwdref_passed_lvalue_int (T&& p) {
+  fail_if_not_same<T, int&>();
+  fail_if_not_same<decltype(p), int&>();
+  fail_if_not_same<decltype((p)), int&>();
+}
+#pragma omp end declare variant
+
+template<typename T>
+void fwdref_passed_lvalue_const_int (T&& p) {
+  static_assert(is_same<T, int const&>::value);
+  static_assert(is_same<decltype(p), int const&>::value);
+  static_assert(is_same<decltype((p)), int const&>::value);
+}
+#pragma omp begin declare variant match (implementation={vendor("gnu")})
+template<typename T>
+void fwdref_passed_lvalue_const_int (T&& p) {
+  fail_if_not_same<T, int const&>();
+  fail_if_not_same<decltype(p), int const&>();
+  fail_if_not_same<decltype((p)), int const&>();
+}
+#pragma omp end declare variant
+
+template<typename T>
+void fwdref_passed_rvalue_int (T&& p) {
+  static_assert(is_same<T, int>::value);
+  static_assert(is_same<decltype(p), int&&>::value);
+  static_assert(is_same<decltype((p)), int&>::value);
+}
+#pragma omp begin declare variant match (implementation={vendor("gnu")})
+template<typename T>
+void fwdref_passed_rvalue_int (T&& p) {
+  fail_if_not_same<T, int>();
+  fail_if_not_same<decltype(p), int&&>();
+  fail_if_not_same<decltype((p)), int&>();
+}
+#pragma omp end declare variant
+
+template<typename T>
+void fwdref_passed_rvalue_const_int (T&& p) {
+  static_assert(is_same<T, int const>::value);
+  static_assert(is_same<decltype(p), int const&&>::value);
+  static_assert(is_same<decltype((p)), int const&>::value);
+}
+#pragma omp begin declare variant match (implementation={vendor("gnu")})
+template<typename T>
+void fwdref_passed_rvalue_const_int (T&& p) {
+  fail_if_not_same<T, int const>();
+  fail_if_not_same<decltype(p), int const&&>();
+  fail_if_not_same<decltype((p)), int const&>();
+}
+#pragma omp end declare variant
+
+void instantiate_fwdref()
+{
+  int lvalue = 0;
+  fwdref_passed_lvalue_int(lvalue);
+  fwdref_passed_lvalue_const_int(static_cast<int const&>(lvalue));
+  fwdref_passed_rvalue_int(0);
+  fwdref_passed_rvalue_const_int(static_cast<int const&&>(0));
+}
+
+
+
+template<typename T>
+void explicit_instantiate_fwdref_with_lvalue_int (T&& p) {
+  static_assert(is_same<T, int&>::value);
+  static_assert(is_same<decltype(p), int&>::value);
+  static_assert(is_same<decltype((p)), int&>::value);
+}
+#pragma omp begin declare variant match (implementation={vendor("gnu")})
+template<typename T>
+void explicit_instantiate_fwdref_with_lvalue_int (T&& p) {
+  fail_if_not_same<T, int&>();
+  fail_if_not_same<decltype(p), int&>();
+  fail_if_not_same<decltype((p)), int&>();
+}
+#pragma omp end declare variant
+
+template<typename T>
+void explicit_instantiate_fwdref_with_lvalue_const_int (T&& p) {
+  static_assert(is_same<T, int const&>::value);
+  static_assert(is_same<decltype(p), int const&>::value);
+  static_assert(is_same<decltype((p)), int const&>::value);
+}
+#pragma omp begin declare variant match (implementation={vendor("gnu")})
+template<typename T>
+void explicit_instantiate_fwdref_with_lvalue_const_int (T&& p) {
+  fail_if_not_same<T, int const&>();
+  fail_if_not_same<decltype(p), int const&>();
+  fail_if_not_same<decltype((p)), int const&>();
+}
+#pragma omp end declare variant
+
+template<typename T>
+void explicit_instantiate_fwdref_with_rvalue_int (T&& p) {
+  static_assert(is_same<T, int&&>::value);
+  static_assert(is_same<decltype(p), int&&>::value);
+  static_assert(is_same<decltype((p)), int&>::value);
+}
+#pragma omp begin declare variant match (implementation={vendor("gnu")})
+template<typename T>
+void explicit_instantiate_fwdref_with_rvalue_int (T&& p) {
+  fail_if_not_same<T, int&&>();
+  fail_if_not_same<decltype(p), int&&>();
+  fail_if_not_same<decltype((p)), int&>();
+}
+#pragma omp end declare variant
+
+template<typename T>
+void explicit_instantiate_fwdref_with_rvalue_const_int (T&& p) {
+  static_assert(is_same<T, int const&&>::value);
+  static_assert(is_same<decltype(p), int const&&>::value);
+  static_assert(is_same<decltype((p)), int const&>::value);
+}
+#pragma omp begin declare variant match (implementation={vendor("gnu")})
+template<typename T>
+void explicit_instantiate_fwdref_with_rvalue_const_int (T&& p) {
+  fail_if_not_same<T, int const&&>();
+  fail_if_not_same<decltype(p), int const&&>();
+  fail_if_not_same<decltype((p)), int const&>();
+}
+#pragma omp end declare variant
+
+/* Technically a missuse of a forwarding reference */
+void explicit_instantiate_fwdref()
+{
+  int lvalue = 0;
+  explicit_instantiate_fwdref_with_lvalue_int<int&>(lvalue);
+  explicit_instantiate_fwdref_with_lvalue_const_int<int const&>(static_cast<int const&>(lvalue));
+  explicit_instantiate_fwdref_with_rvalue_int<int&&>(0);  // { dg-bogus "required from here" "PR118791" { xfail *-*-* } }
+  explicit_instantiate_fwdref_with_rvalue_const_int<int const&&>(static_cast<int const&&>(0));  // { dg-bogus "required from here" "PR118791" { xfail *-*-* } }
+}
+
+
+template<typename T>
+void const_lref_passed_lvalue_int (T const& p) {
+  static_assert(is_same<T, int>::value);
+  static_assert(is_same<decltype(p), int const&>::value);
+  static_assert(is_same<decltype((p)), int const&>::value);
+}
+#pragma omp begin declare variant match (implementation={vendor("gnu")})
+template<typename T>
+void const_lref_passed_lvalue_int (T const& p) {
+  fail_if_not_same<T, int>();
+  fail_if_not_same<decltype(p), int const&>();
+  fail_if_not_same<decltype((p)), int const&>();
+}
+#pragma omp end declare variant
+
+template<typename T>
+void const_lref_passed_lvalue_const_int (T const& p) {
+  static_assert(is_same<T, int>::value);
+  static_assert(is_same<decltype(p), int const&>::value);
+  static_assert(is_same<decltype((p)), int const&>::value);
+}
+#pragma omp begin declare variant match (implementation={vendor("gnu")})
+template<typename T>
+void const_lref_passed_lvalue_const_int (T const& p) {
+  fail_if_not_same<T, int>();
+  fail_if_not_same<decltype(p), int const&>();
+  fail_if_not_same<decltype((p)), int const&>();
+}
+#pragma omp end declare variant
+
+void instantiate_const_lref()
+{
+  int lvalue = 0;
+  const_lref_passed_lvalue_int(lvalue);
+  const_lref_passed_lvalue_const_int(static_cast<int const&>(lvalue));
+}
diff --git a/gcc/testsuite/g++.dg/gomp/delim-declare-variant-71.C b/gcc/testsuite/g++.dg/gomp/delim-declare-variant-71.C
new file mode 100644
index 00000000000..7bd59d07c32
--- /dev/null
+++ b/gcc/testsuite/g++.dg/gomp/delim-declare-variant-71.C
@@ -0,0 +1,157 @@
+/* { dg-do compile { target c++11 } } */
+
+/* Test static_assert in variants.  */
+/* Most of the tests in this file are broken and xfailed.
+   See also delim-declare-variant-41.C for a simpler test case for
+   the "base function cannot be resolved" sorry.  */
+
+struct has_value_true { static constexpr bool value = true; };
+
+template<typename T>
+struct always_true {
+  static constexpr bool value = true;
+};
+
+template<typename T>
+void static_assert_in_variant_static_member_uninstantiated (T) { }
+#pragma omp begin declare variant match (implementation={vendor("gnu")})
+template<typename T>
+void static_assert_in_variant_static_member_uninstantiated (T)  // { dg-bogus "base function cannot be resolved" "" { xfail *-*-* } }
+{
+  static_assert(T::value);
+}
+#pragma omp end declare variant
+
+template<typename T>
+void static_assert_in_variant_static_member_no_param_uninstantiated () { }
+#pragma omp begin declare variant match (implementation={vendor("gnu")})
+template<typename T>
+void static_assert_in_variant_static_member_no_param_uninstantiated ()  // { dg-bogus "base function cannot be resolved" "" { xfail *-*-* } }
+{
+  static_assert(T::value);
+}
+#pragma omp end declare variant
+
+template<typename T>
+void static_assert_in_variant_static_member (T) { }
+#pragma omp begin declare variant match (implementation={vendor("gnu")})
+template<typename T>
+void static_assert_in_variant_static_member (T)  // { dg-bogus "base function cannot be resolved" "" { xfail *-*-* } }
+{
+  static_assert(T::value);
+}
+#pragma omp end declare variant
+
+template<typename T>
+void static_assert_in_variant_static_member_no_param () { }
+#pragma omp begin declare variant match (implementation={vendor("gnu")})
+template<typename T>
+void static_assert_in_variant_static_member_no_param ()  // { dg-bogus "base function cannot be resolved" "" { xfail *-*-* } }
+{
+  static_assert(T::value);
+}
+#pragma omp end declare variant
+
+void instantiate_static_assert_in_variant_static_member()
+{
+  static_assert_in_variant_static_member(has_value_true{});
+  static_assert_in_variant_static_member_no_param<has_value_true>();
+}
+
+
+template<typename T>
+void static_assert_in_variant_templ_member_uninstantiated (T) { }
+#pragma omp begin declare variant match (implementation={vendor("gnu")})
+template<typename T>
+void static_assert_in_variant_templ_member_uninstantiated (T)  // { dg-bogus "base function cannot be resolved" "" { xfail *-*-* } }
+{
+  static_assert(always_true<T>::value);
+}
+#pragma omp end declare variant
+
+template<typename T>
+void static_assert_in_variant_templ_member_no_param_uninstantiated () { }
+#pragma omp begin declare variant match (implementation={vendor("gnu")})
+template<typename T>
+void static_assert_in_variant_templ_member_no_param_uninstantiated ()  // { dg-bogus "base function cannot be resolved" "" { xfail *-*-* } }
+{
+  static_assert(always_true<T>::value);
+}
+#pragma omp end declare variant
+
+template<typename T>
+void static_assert_in_variant_templ_member (T) { }
+#pragma omp begin declare variant match (implementation={vendor("gnu")})
+template<typename T>
+void static_assert_in_variant_templ_member (T)  // { dg-bogus "base function cannot be resolved" "" { xfail *-*-* } }
+{
+  static_assert(always_true<T>::value);
+}
+#pragma omp end declare variant
+
+template<typename T>
+void static_assert_in_variant_templ_member_no_param () { }
+#pragma omp begin declare variant match (implementation={vendor("gnu")})
+template<typename T>
+void static_assert_in_variant_templ_member_no_param ()  // { dg-bogus "base function cannot be resolved" "" { xfail *-*-* } }
+{
+  static_assert(always_true<T>::value);
+}
+#pragma omp end declare variant
+
+void instantiate_static_assert_in_variant_templ_member()
+{
+  static_assert_in_variant_templ_member(0);
+  static_assert_in_variant_templ_member_no_param<int>();
+}
+
+
+/* PR118530 affects also the non-delimited form of "declare variant".  */
+template<bool B>
+void static_assert_in_variant_nttp_uninstantiated () { }
+#pragma omp begin declare variant match (implementation={vendor("gnu")})
+template<bool B>
+void static_assert_in_variant_nttp_uninstantiated () {
+  static_assert(B);
+}
+#pragma omp end declare variant
+
+template<bool B>
+void static_assert_in_variant_nttp () { }  // { dg-bogus "no matching function for call" "PR118530" { xfail *-*-* } }
+#pragma omp begin declare variant match (implementation={vendor("gnu")})
+template<bool B>
+void static_assert_in_variant_nttp () {
+  static_assert(B);
+}
+#pragma omp end declare variant
+
+void instantiate_static_assert_in_variant_nttp()
+{
+  static_assert_in_variant_nttp<true>();
+}
+
+
+template<template<typename> class Templ>
+void static_assert_in_variant_template_template_uninstantiated () { }
+#pragma omp begin declare variant match (implementation={vendor("gnu")})
+template<template<typename> class Templ>
+void static_assert_in_variant_template_template_uninstantiated ()  // { dg-bogus "base function cannot be resolved" "" { xfail *-*-* } }
+{
+  static_assert(Templ<void>::value);
+}
+#pragma omp end declare variant
+
+template<template<typename> class Templ>
+void static_assert_in_variant_template_template () { }
+#pragma omp begin declare variant match (implementation={vendor("gnu")})
+template<template<typename> class Templ>
+void static_assert_in_variant_template_template ()  // { dg-bogus "base function cannot be resolved" "" { xfail *-*-* } }
+{
+  static_assert(Templ<void>::value);
+}
+#pragma omp end declare variant
+
+void instantiate_static_assert_in_variant_template_template()
+{
+  static_assert_in_variant_template_template<always_true>();
+}
diff --git a/libgomp/testsuite/libgomp.c++/delim-declare-variant-1.C b/libgomp/testsuite/libgomp.c++/delim-declare-variant-1.C
new file mode 100644
index 00000000000..bf146dd2365
--- /dev/null
+++ b/libgomp/testsuite/libgomp.c++/delim-declare-variant-1.C
@@ -0,0 +1,29 @@
+/* { dg-additional-options "-foffload=disable" } */
+
+/* Check that variants within a "begin declare variant" directive
+   are attached to the correct overloaded function.  */
+
+int f (int x) { return x; }
+
+#pragma omp begin declare variant match (implementation={vendor("gnu")})
+int f (int x) { return -1; }
+#pragma omp end declare variant
+
+int f (int x, int y) { return x * y; }
+
+#pragma omp begin declare variant match (construct={target})
+int f (int x, int y) { return -2; }
+#pragma omp end declare variant
+
+int f (int x, int y, int z) { return x * y * z; }
+
+#pragma omp begin declare variant match (device={kind("host")})
+int f (int x, int y, int z) { return -3; }
+#pragma omp end declare variant
+
+int main (void)
+{
+  if (f (10) != -1) __builtin_abort ();
+  if (f (10, 20) != 200) __builtin_abort ();   /* no match on this one */
+  if (f (10, 20, 30) != -3) __builtin_abort ();
+}
diff --git a/libgomp/testsuite/libgomp.c++/delim-declare-variant-2.C b/libgomp/testsuite/libgomp.c++/delim-declare-variant-2.C
new file mode 100644
index 00000000000..6641768c1ef
--- /dev/null
+++ b/libgomp/testsuite/libgomp.c++/delim-declare-variant-2.C
@@ -0,0 +1,37 @@
+/* Check that "omp begin declare variant" works on methods in a
+   class declaration.  */
+
+class test1 {
+
+ private:
+  int n;
+  static int m;
+
+ public:
+
+  void set_n (int x) { n = x; }
+  int get_n (void) { return n; }
+
+  static void set_m (int x) { m = x; }
+  static int get_m (void) { return m; }
+
+  #pragma omp begin declare variant match (implementation={vendor("gnu")})
+  int get_n (void) { return n * 2; }
+  static int get_m (void) { return m * 2; }
+  #pragma omp end declare variant
+
+  #pragma omp begin declare variant match (construct={target})
+  int get_n (void) { return this->n * 2; }
+  #pragma omp end declare variant
+};
+
+int test1::m;
+
+int main (void)
+{
+  test1 t1;
+  t1.set_n (10);
+  if (t1.get_n () != 20) __builtin_abort ();
+  test1::set_m (1);
+  if (test1::get_m () != 2) __builtin_abort ();
+}
diff --git a/libgomp/testsuite/libgomp.c++/delim-declare-variant-7.C b/libgomp/testsuite/libgomp.c++/delim-declare-variant-7.C
new file mode 100644
index 00000000000..60cc5d8a55b
--- /dev/null
+++ b/libgomp/testsuite/libgomp.c++/delim-declare-variant-7.C
@@ -0,0 +1,39 @@
+/* Check that "omp begin declare variant" works on methods in a template
+   class declaration.  */
+
+template <typename T>
+class test1 {
+
+ private:
+  T n;
+  static T m;
+
+ public:
+
+  void set_n (T x) { n = x; }
+  T get_n (void) { return n; }
+
+  static void set_m (T x) { m = x; }
+  static T get_m (void) { return m; }
+
+  #pragma omp begin declare variant match (implementation={vendor("gnu")})
+  T get_n (void) { return n * 2; }
+  static T get_m (void) { return m * 2; }
+  #pragma omp end declare variant
+
+  #pragma omp begin declare variant match (construct={target})
+  T get_n (void) { return this->n * 2; }
+  #pragma omp end declare variant
+};
+
+template <typename T>
+T test1<T>::m;
+
+int main (void)
+{
+  test1<int> t1;
+  t1.set_n (10);
+  if (t1.get_n () != 20) __builtin_abort ();
+  test1<int>::set_m (1);
+  if (test1<int>::get_m () != 2) __builtin_abort ();
+}
