Patchwork C++ PATCH to correct overflow handling

login
register
mail settings
Submitter Jason Merrill
Date Nov. 8, 2010, 8:16 p.m.
Message ID <4CD85AB4.1070105@redhat.com>
Download mbox | patch
Permalink /patch/70453/
State New
Headers show

Comments

Jason Merrill - Nov. 8, 2010, 8:16 p.m.
As recently discussed, in C++0x mode we were incorrectly treating 
(int)(unsigned)-1 as a non-constant expression; the standard does not 
consider that to be overflow, even though the middle end does.  This 
patch fixes that.  It also changes arithmetic overflow to be handled the 
same way in both C++98 and C++0x modes, making it a permerror rather 
than an error (previous C++0x) or conditional pedwarn (C++98).

Tested x86_64-pc-linux-gnu, applied to trunk.
commit 332427e690734016e14badac94e354d61cac812c
Author: Jason Merrill <jason@redhat.com>
Date:   Sun Nov 7 17:12:02 2010 -0500

    	Correct conversion/overflow behavior.
    	* cvt.c (ignore_overflows): Move here from typeck.c.
    	(ocp_convert): Use it.
    	(cp_fold_convert): Use it.  Don't call rvalue.
    	* typeck.c (build_static_cast_1): Don't use it.  Do call rvalue.
    	* error.c (location_of): Handle expressions, too.
    	* class.c (check_bitfield_decl): Set input_location around call to
    	cxx_constant_value.
    	* semantics.c (cxx_eval_outermost_constant_expr): Don't
    	print the expression if it already had TREE_OVERFLOW set.
    	(reduced_constant_expression_p): Check TREE_OVERFLOW_P for C++98, too.
    	(verify_constant): Allow overflow with a permerror if we're
    	enforcing.
    	(cxx_eval_outermost_constant_expr): Use verify_constant.
    	(adjust_temp_type): Use cp_fold_convert.
    	* decl.c (build_enumerator): Don't call constant_expression_warning.
    	* decl2.c (grokbitfield): Likewise.

Patch

diff --git a/gcc/cp/class.c b/gcc/cp/class.c
index 435fa71..03951cf 100644
--- a/gcc/cp/class.c
+++ b/gcc/cp/class.c
@@ -2797,11 +2797,14 @@  check_bitfield_decl (tree field)
     }
   else
     {
+      location_t loc = input_location;
       /* Avoid the non_lvalue wrapper added by fold for PLUS_EXPRs.  */
       STRIP_NOPS (w);
 
       /* detect invalid field size.  */
+      input_location = DECL_SOURCE_LOCATION (field);
       w = cxx_constant_value (w);
+      input_location = loc;
 
       if (TREE_CODE (w) != INTEGER_CST)
 	{
diff --git a/gcc/cp/cvt.c b/gcc/cp/cvt.c
index d2d6f4a..2f7823f 100644
--- a/gcc/cp/cvt.c
+++ b/gcc/cp/cvt.c
@@ -543,12 +543,35 @@  force_rvalue (tree expr)
 }
 
 
-/* Fold away simple conversions, but make sure the result is an rvalue.  */
+/* If EXPR and ORIG are INTEGER_CSTs, return a version of EXPR that has
+   TREE_OVERFLOW set only if it is set in ORIG.  Otherwise, return EXPR
+   unchanged.  */
+
+static tree
+ignore_overflows (tree expr, tree orig)
+{
+  if (TREE_CODE (expr) == INTEGER_CST
+      && TREE_CODE (orig) == INTEGER_CST
+      && TREE_OVERFLOW (expr) != TREE_OVERFLOW (orig))
+    {
+      gcc_assert (!TREE_OVERFLOW (orig));
+      /* Ensure constant sharing.  */
+      expr = build_int_cst_wide (TREE_TYPE (expr),
+				 TREE_INT_CST_LOW (expr),
+				 TREE_INT_CST_HIGH (expr));
+    }
+  return expr;
+}
+
+/* Fold away simple conversions, but make sure TREE_OVERFLOW is set
+   properly.  */
 
 tree
 cp_fold_convert (tree type, tree expr)
 {
-  return rvalue (fold_convert (type, expr));
+  tree conv = fold_convert (type, expr);
+  conv = ignore_overflows (conv, expr);
+  return conv;
 }
 
 /* C++ conversions, preference to static cast conversions.  */
@@ -661,6 +684,7 @@  ocp_convert (tree type, tree expr, int convtype, int flags)
   if (INTEGRAL_CODE_P (code))
     {
       tree intype = TREE_TYPE (e);
+      tree converted;
 
       if (TREE_CODE (type) == ENUMERAL_TYPE)
 	{
@@ -705,7 +729,10 @@  ocp_convert (tree type, tree expr, int convtype, int flags)
       if (code == BOOLEAN_TYPE)
 	return cp_truthvalue_conversion (e);
 
-      return fold_if_not_in_template (convert_to_integer (type, e));
+      converted = fold_if_not_in_template (convert_to_integer (type, e));
+
+      /* Ignore any integer overflow caused by the conversion.  */
+      return ignore_overflows (converted, e);
     }
   if (NULLPTR_TYPE_P (type) && e && null_ptr_cst_p (e))
     return nullptr_node;
diff --git a/gcc/cp/decl.c b/gcc/cp/decl.c
index fb5ca7f..c372840 100644
--- a/gcc/cp/decl.c
+++ b/gcc/cp/decl.c
@@ -11823,7 +11823,6 @@  build_enumerator (tree name, tree value, tree enumtype, location_t loc)
 	  if (TREE_CODE (value) == INTEGER_CST)
 	    {
 	      value = perform_integral_promotions (value);
-	      constant_expression_warning (value);
 	    }
 	  else
 	    {
diff --git a/gcc/cp/decl2.c b/gcc/cp/decl2.c
index a805c6b..e00549e 100644
--- a/gcc/cp/decl2.c
+++ b/gcc/cp/decl2.c
@@ -1054,7 +1054,6 @@  grokbitfield (const cp_declarator *declarator,
       if (!INTEGRAL_OR_UNSCOPED_ENUMERATION_TYPE_P (TREE_TYPE (width)))
 	error ("width of bit-field %qD has non-integral type %qT", value,
 	       TREE_TYPE (width));
-      constant_expression_warning (width);
       DECL_INITIAL (value) = width;
       SET_DECL_C_BIT_FIELD (value);
     }
diff --git a/gcc/cp/error.c b/gcc/cp/error.c
index 6f60c06..1560fc6 100644
--- a/gcc/cp/error.c
+++ b/gcc/cp/error.c
@@ -2469,7 +2469,9 @@  location_of (tree t)
   else if (TREE_CODE (t) == OVERLOAD)
     t = OVL_FUNCTION (t);
 
-  return DECL_SOURCE_LOCATION (t);
+  if (DECL_P (t))
+    return DECL_SOURCE_LOCATION (t);
+  return EXPR_LOC_OR_HERE (t);
 }
 
 /* Now the interfaces from error et al to dump_type et al. Each takes an
diff --git a/gcc/cp/semantics.c b/gcc/cp/semantics.c
index fd7da34..494247e7 100644
--- a/gcc/cp/semantics.c
+++ b/gcc/cp/semantics.c
@@ -5789,7 +5789,7 @@  adjust_temp_type (tree type, tree temp)
   if (TREE_CODE (temp) == CONSTRUCTOR)
     return build_constructor (type, CONSTRUCTOR_ELTS (temp));
   gcc_assert (SCALAR_TYPE_P (type));
-  return fold_convert (type, temp);
+  return cp_fold_convert (type, temp);
 }
 
 /* Subroutine of cxx_eval_call_expression.
@@ -6003,13 +6003,13 @@  cxx_eval_call_expression (const constexpr_call *old_call, tree t,
   return result;
 }
 
+/* FIXME speed this up, it's taking 16% of compile time on sieve testcase.  */
+
 bool
 reduced_constant_expression_p (tree t)
 {
-  /* FIXME speed this up, it's taking 16% of compile time on sieve testcase.  */
-  if (cxx_dialect >= cxx0x && TREE_OVERFLOW_P (t))
-    /* In C++0x, integer overflow makes this not a constant expression.
-       FIXME arithmetic overflow is different from conversion truncation */
+  if (TREE_OVERFLOW_P (t))
+    /* Integer overflow makes this not a constant expression.  */
     return false;
   /* FIXME are we calling this too much?  */
   return initializer_constant_valid_p (t, TREE_TYPE (t)) != NULL_TREE;
@@ -6030,7 +6030,20 @@  verify_constant (tree t, bool allow_non_constant, bool *non_constant_p)
   if (!*non_constant_p && !reduced_constant_expression_p (t))
     {
       if (!allow_non_constant)
-	error ("%qE is not a constant expression", t);
+	{
+	  /* If T was already folded to a _CST with TREE_OVERFLOW set,
+	     printing the folded constant isn't helpful.  */
+	  if (TREE_OVERFLOW_P (t))
+	    {
+	      permerror (input_location, "overflow in constant expression");
+	      /* If we're being permissive (and are in an enforcing
+		 context), consider this constant.  */
+	      if (flag_permissive)
+		return false;
+	    }
+	  else
+	    error ("%q+E is not a constant expression", t);
+	}
       *non_constant_p = true;
     }
   return *non_constant_p;
@@ -6895,12 +6908,7 @@  cxx_eval_outermost_constant_expr (tree t, bool allow_non_constant)
   tree r = cxx_eval_constant_expression (NULL, t, allow_non_constant,
 					 false, &non_constant_p);
 
-  if (!non_constant_p && !reduced_constant_expression_p (r))
-    {
-      if (!allow_non_constant)
-	error ("%qE is not a constant expression", t);
-      non_constant_p = true;
-    }
+  verify_constant (r, allow_non_constant, &non_constant_p);
 
   if (non_constant_p && !allow_non_constant)
     return error_mark_node;
diff --git a/gcc/cp/typeck.c b/gcc/cp/typeck.c
index 6a52fc4..cad8817 100644
--- a/gcc/cp/typeck.c
+++ b/gcc/cp/typeck.c
@@ -5717,33 +5717,6 @@  convert_ptrmem (tree type, tree expr, bool allow_inverse_p,
 			     allow_inverse_p, c_cast_p, complain);
 }
 
-/* If EXPR is an INTEGER_CST and ORIG is an arithmetic constant, return
-   a version of EXPR that has TREE_OVERFLOW set if it is set in ORIG.
-   Otherwise, return EXPR unchanged.  */
-
-static tree
-ignore_overflows (tree expr, tree orig)
-{
-  if (TREE_CODE (expr) == INTEGER_CST
-      && CONSTANT_CLASS_P (orig)
-      && TREE_CODE (orig) != STRING_CST
-      && TREE_OVERFLOW (expr) != TREE_OVERFLOW (orig))
-    {
-      if (!TREE_OVERFLOW (orig))
-	/* Ensure constant sharing.  */
-	expr = build_int_cst_wide (TREE_TYPE (expr),
-				   TREE_INT_CST_LOW (expr),
-				   TREE_INT_CST_HIGH (expr));
-      else
-	{
-	  /* Avoid clobbering a shared constant.  */
-	  expr = copy_node (expr);
-	  TREE_OVERFLOW (expr) = TREE_OVERFLOW (orig);
-	}
-    }
-  return expr;
-}
-
 /* Perform a static_cast from EXPR to TYPE.  When C_CAST_P is true,
    this static_cast is being attempted as one of the possible casts
    allowed by a C-style cast.  (In that case, accessibility of base
@@ -5757,7 +5730,6 @@  build_static_cast_1 (tree type, tree expr, bool c_cast_p,
 {
   tree intype;
   tree result;
-  tree orig;
 
   /* Assume the cast is valid.  */
   *valid_p = true;
@@ -5814,8 +5786,14 @@  build_static_cast_1 (tree type, tree expr, bool c_cast_p,
       expr = build_base_path (MINUS_EXPR, build_address (expr),
 			      base, /*nonnull=*/false);
       /* Convert the pointer to a reference -- but then remember that
-	 there are no expressions with reference type in C++.  */
-      return convert_from_reference (cp_fold_convert (type, expr));
+	 there are no expressions with reference type in C++.
+
+         We call rvalue so that there's an actual tree code
+         (NON_LVALUE_EXPR) for the static_cast; otherwise, if the operand
+         is a variable with the same type, the conversion would get folded
+         away, leaving just the variable and causing lvalue_kind to give
+         the wrong answer.  */
+      return convert_from_reference (rvalue (cp_fold_convert (type, expr)));
     }
 
   /* "An lvalue of type cv1 T1 can be cast to type rvalue reference to
@@ -5830,8 +5808,6 @@  build_static_cast_1 (tree type, tree expr, bool c_cast_p,
       return convert_from_reference (expr);
     }
 
-  orig = expr;
-
   /* Resolve overloaded address here rather than once in
      implicit_conversion and again in the inverse code below.  */
   if (TYPE_PTRMEMFUNC_P (type) && type_unknown_p (expr))
@@ -5852,9 +5828,6 @@  build_static_cast_1 (tree type, tree expr, bool c_cast_p,
     {
       result = convert_from_reference (result);
 
-      /* Ignore any integer overflow caused by the cast.  */
-      result = ignore_overflows (result, orig);
-
       /* [expr.static.cast]
 
 	 If T is a reference type, the result is an lvalue; otherwise,
@@ -5894,13 +5867,7 @@  build_static_cast_1 (tree type, tree expr, bool c_cast_p,
        || SCALAR_FLOAT_TYPE_P (type))
       && (INTEGRAL_OR_ENUMERATION_TYPE_P (intype)
 	  || SCALAR_FLOAT_TYPE_P (intype)))
-    {
-      expr = ocp_convert (type, expr, CONV_C_CAST, LOOKUP_NORMAL);
-
-      /* Ignore any integer overflow caused by the cast.  */
-      expr = ignore_overflows (expr, orig);
-      return expr;
-    }
+    return ocp_convert (type, expr, CONV_C_CAST, LOOKUP_NORMAL);
 
   if (TYPE_PTR_P (type) && TYPE_PTR_P (intype)
       && CLASS_TYPE_P (TREE_TYPE (type))
diff --git a/gcc/testsuite/g++.dg/expr/overflow1.C b/gcc/testsuite/g++.dg/expr/overflow1.C
new file mode 100644
index 0000000..b67b4e4
--- /dev/null
+++ b/gcc/testsuite/g++.dg/expr/overflow1.C
@@ -0,0 +1,8 @@ 
+#include <limits.h>
+
+enum E {
+  A = (unsigned char)-1,	/* OK */
+  B = (signed char)UCHAR_MAX,	/* implementation-defined */
+  C = INT_MAX+1,     /* undefined (C)/ill-formed (C++) { dg-message "" } */
+  D = UINT_MAX+1     /* OK */
+};
diff --git a/gcc/testsuite/g++.dg/warn/overflow-warn-1.C b/gcc/testsuite/g++.dg/warn/overflow-warn-1.C
index 17bd067..22c512a 100644
--- a/gcc/testsuite/g++.dg/warn/overflow-warn-1.C
+++ b/gcc/testsuite/g++.dg/warn/overflow-warn-1.C
@@ -1,7 +1,7 @@ 
 /* Test for diagnostics for constant overflow.  */
 /* Origin: Joseph Myers <joseph@codesourcery.com> */
 /* { dg-do compile } */
-/* { dg-options "" } */
+/* { dg-options "-fpermissive" } */
 
 #include <limits.h>
 
@@ -19,8 +19,10 @@  enum e {
   E4 = 0 * (1 / 0), /* { dg-warning "division by zero" } */
   /* { dg-error "enumerator value for 'E4' is not an integer constant" "enum error" { xfail *-*-* } 19 } */
   E5 = INT_MAX + 1, /* { dg-warning "integer overflow in expression" } */
+  /* { dg-warning "overflow in constant expression" "constant" { target *-*-* } 21 } */
   /* Again, overflow in evaluated subexpression.  */
   E6 = 0 * (INT_MAX + 1), /* { dg-warning "integer overflow in expression" } */
+  /* { dg-warning "overflow in constant expression" "constant" { target *-*-* } 24 } */
   /* A cast does not constitute overflow in conversion.  */
   E7 = (char) INT_MAX
 };
@@ -29,6 +31,7 @@  struct s {
   int a;
   int : 0 * (1 / 0); /* { dg-warning "division by zero" } */
   int : 0 * (INT_MAX + 1); /* { dg-warning "integer overflow in expression" } */
+  /* { dg-warning "overflow in constant expression" "constant" { target *-*-* } 33 } */
 };
 
 void
@@ -49,10 +52,10 @@  void *n = 0;
    constants.  The third has the overflow in an unevaluated
    subexpression, so is a null pointer constant.  */
 void *p = 0 * (INT_MAX + 1); /* { dg-warning "integer overflow in expression" } */
-/* { dg-error "invalid conversion from 'int' to 'void" "null" { target *-*-* } 51 } */
+/* { dg-warning "invalid conversion from 'int' to 'void" "null" { target *-*-* } 54 } */
 void *q = 0 * (1 / 0); /* { dg-warning "division by zero" } */
-/* { dg-error "invalid conversion from 'int' to 'void*'" "null" { xfail *-*-* } 53 } */
-void *r = (1 ? 0 : INT_MAX+1); /* { dg-bogus "integer overflow in expression" "" { xfail *-*-* } 55 } */
+/* { dg-error "invalid conversion from 'int' to 'void*'" "null" { xfail *-*-* } 56 } */
+void *r = (1 ? 0 : INT_MAX+1); /* { dg-bogus "integer overflow in expression" "" { xfail *-*-* } } */
 
 void
 g (int i)
@@ -62,6 +65,7 @@  g (int i)
     case 0 * (1/0): /* { dg-warning "division by zero" } */
       ;
     case 1 + 0 * (INT_MAX + 1): /* { dg-warning "integer overflow in expression" } */
+      /* { dg-warning "overflow in constant expression" "constant" { target *-*-* } 67 } */
       ;
     }
 }
diff --git a/gcc/testsuite/g++.dg/warn/overflow-warn-3.C b/gcc/testsuite/g++.dg/warn/overflow-warn-3.C
index ce03a97..d88c87a 100644
--- a/gcc/testsuite/g++.dg/warn/overflow-warn-3.C
+++ b/gcc/testsuite/g++.dg/warn/overflow-warn-3.C
@@ -57,7 +57,7 @@  void *p = 0 * (INT_MAX + 1); /* { dg-warning "integer overflow in expression" }
 
 void *q = 0 * (1 / 0); /* { dg-warning "division by zero" } */
 /* { dg-warning "invalid conversion from 'int' to 'void*'" "null" { xfail *-*-* } 58 } */
-void *r = (1 ? 0 : INT_MAX+1); /* { dg-bogus "integer overflow in expression" "" { xfail *-*-* } 60 } */
+void *r = (1 ? 0 : INT_MAX+1); /* { dg-bogus "integer overflow in expression" "" { xfail *-*-* } } */
 
 void
 g (int i)
diff --git a/gcc/testsuite/g++.dg/warn/overflow-warn-4.C b/gcc/testsuite/g++.dg/warn/overflow-warn-4.C
index 0c916d0..374d294 100644
--- a/gcc/testsuite/g++.dg/warn/overflow-warn-4.C
+++ b/gcc/testsuite/g++.dg/warn/overflow-warn-4.C
@@ -11,7 +11,7 @@  enum e {
   E1 = UINT_MAX + 1,
   /* Overflow in an unevaluated part of an expression is OK (example
      in the standard).  */
-  E2 = 2 || 1 / 0, /* { dg-bogus "warning: division by zero" "" { xfail *-*-* } 14 } */
+  E2 = 2 || 1 / 0, /* { dg-bogus "warning: division by zero" "" { xfail *-*-* } } */
   E3 = 1 / 0, /* { dg-warning "division by zero" } */
   /* { dg-error "enumerator value for 'E3' is not an integer constant|not a constant expression" "enum error" { target *-*-* } 15 } */
   /* But as in DR#031, the 1/0 in an evaluated subexpression means the
@@ -20,9 +20,11 @@  enum e {
   /* { dg-error "enumerator value for 'E4' is not an integer constant" "enum error" { xfail *-*-* } 19 } */
   E5 = INT_MAX + 1, /* { dg-warning "integer overflow in expression" } */
   /* { dg-error "overflow in constant expression" "constant" { target *-*-* } 21 } */
+  /* { dg-error "enumerator value for 'E5' is not an integer constant" "enum error" { target *-*-* } 21 } */
   /* Again, overflow in evaluated subexpression.  */
   E6 = 0 * (INT_MAX + 1), /* { dg-warning "integer overflow in expression" } */
-  /* { dg-error "overflow in constant expression" "constant" { target *-*-* } 24 } */
+  /* { dg-error "overflow in constant expression" "constant" { target *-*-* } 25 } */
+  /* { dg-error "enumerator value for 'E6' is not an integer constant" "enum error" { target *-*-* } 25 } */
   /* A cast does not constitute overflow in conversion.  */
   E7 = (char) INT_MAX
 };
@@ -31,7 +33,8 @@  struct s {
   int a;
   int : 0 * (1 / 0); /* { dg-warning "division by zero" } */
   int : 0 * (INT_MAX + 1); /* { dg-warning "integer overflow in expression" } */
-  /* { dg-error "overflow in constant expression" "constant" { target *-*-* } 33 } */
+  /* { dg-error "overflow in constant expression" "constant" { target *-*-* } 35 } */
+  /* { dg-error "bit-field .* width not an integer constant" "" { target *-*-* } 35 } */
 };
 
 void
@@ -53,11 +56,11 @@  void *n = 0;
    constants.  The third has the overflow in an unevaluated
    subexpression, so is a null pointer constant.  */
 void *p = 0 * (INT_MAX + 1); /* { dg-warning "integer overflow in expression" } */
-/* { dg-error "invalid conversion from 'int' to 'void" "null" { target *-*-* } 55 } */
+/* { dg-error "invalid conversion from 'int' to 'void" "null" { target *-*-* } 58 } */
 
 void *q = 0 * (1 / 0); /* { dg-warning "division by zero" } */
-/* { dg-error "invalid conversion from 'int' to 'void*'" "null" { xfail *-*-* } 58 } */
-void *r = (1 ? 0 : INT_MAX+1); /* { dg-bogus "integer overflow in expression" "" { xfail *-*-* } 60 } */
+/* { dg-error "invalid conversion from 'int' to 'void*'" "null" { xfail *-*-* } 61 } */
+void *r = (1 ? 0 : INT_MAX+1); /* { dg-bogus "integer overflow in expression" "" { xfail *-*-* } } */
 
 void
 g (int i)
@@ -67,7 +70,7 @@  g (int i)
     case 0 * (1/0): /* { dg-warning "division by zero" } */
       ;
     case 1 + 0 * (INT_MAX + 1): /* { dg-warning "integer overflow in expression" } */
-      /* { dg-error "overflow in constant expression" "constant" { target *-*-* } 69 } */
+      /* { dg-error "overflow in constant expression" "constant" { target *-*-* } 72 } */
       ;
     }
 }
diff --git a/gcc/testsuite/g++.dg/warn/overflow-warn-5.C b/gcc/testsuite/g++.dg/warn/overflow-warn-5.C
index 472d4a7..bdfec4a 100644
--- a/gcc/testsuite/g++.dg/warn/overflow-warn-5.C
+++ b/gcc/testsuite/g++.dg/warn/overflow-warn-5.C
@@ -1,7 +1,11 @@ 
 /* PR c/27273 */
 /* { dg-do compile } */
-/* { dg-options "-Woverflow" } */
+
+// This used to warn about "overflow in implicit constant conversion",
+// which was wrong; 512 is never converted to unsigned char.  Rather, an
+// appropriate warning would be that the & expression always evaluates to 0
+// because of the limited range of unsigned char.
 
 unsigned char rx_async(unsigned char p) {
-    return p & 512; /* { dg-warning "overflow in implicit constant conversion" } */
+    return p & 512; /* { dg-warning "" "" { xfail *-*-* } } */
 }
diff --git a/libstdc++-v3/testsuite/20_util/ratio/cons/cons_overflow_neg.cc b/libstdc++-v3/testsuite/20_util/ratio/cons/cons_overflow_neg.cc
index fa4c85e..e7f448d 100644
--- a/libstdc++-v3/testsuite/20_util/ratio/cons/cons_overflow_neg.cc
+++ b/libstdc++-v3/testsuite/20_util/ratio/cons/cons_overflow_neg.cc
@@ -52,6 +52,6 @@  test04()
 // { dg-error "denominator cannot be zero" "" { target *-*-* } 153 }
 // { dg-error "out of range" "" { target *-*-* } 154 }
 // { dg-error "non-constant expression" "" { target *-*-* } 59 }
-// { dg-error "is not a constant expression" "" { target *-*-* } 59 }
+// { dg-error "overflow in constant expression" "" { target *-*-* } 59 }
 // { dg-error "not a member" "" { target *-*-* } 162 }
 // { dg-error "not a valid template argument" "" { target *-*-* } 164 }