diff mbox

C++ PATCH for c++/81359, Unparsed NSDMI error in SFINAE context

Message ID CADzB+2m2cCvczTYyzRk5vE=wnRz037oCwAExA5f55+CBFUzswg@mail.gmail.com
State New
Headers show

Commit Message

Jason Merrill Aug. 9, 2017, 6:30 p.m. UTC
The issue here is that we try to determine the EH specification of
B::C::C() from within SFINAE context, and we can't determine it yet
because the NSDMI for B::C::i hasn't been parsed yet.  This patch
allows that determination to fail quietly in SFINAE context; we'll try
again the next time it is needed.

Tested x86_64-pc-linux-gnu, applying to trunk.
commit 616193fb149d17e8a671a46c03dca98aecbcc752
Author: Jason Merrill <jason@redhat.com>
Date:   Tue Aug 8 12:49:42 2017 -0400

            PR c++/81359 - Unparsed NSDMI error from SFINAE context.
    
            * init.c (get_nsdmi): Add complain parm.
            * typeck2.c (digest_nsdmi_init): Add complain parm.
            (process_init_constructor_record): Pass complain to get_nsdmi.
            * pt.c (maybe_instantiate_noexcept): Add complain parm, return bool.
            * method.c (get_defaulted_eh_spec): Add complain parm.  Pass it into
            synthesized_method_walk.
            (synthesized_method_walk): Adjust.
            (walk_field_subobs): Pass complain to get_nsdmi.
            (defaulted_late_check): Skip other checks if deleted.
            * decl2.c (mark_used): Pass complain to maybe_instantiate_noexcept.
            * call.c (build_aggr_conv): Pass complain to get_nsdmi.
            * parser.c (defarg_location): New.
            * error.c (location_of): Use it.

Comments

Markus Trippelsdorf Aug. 10, 2017, 7:13 a.m. UTC | #1
On 2017.08.09 at 14:30 -0400, Jason Merrill wrote:
> The issue here is that we try to determine the EH specification of
> B::C::C() from within SFINAE context, and we can't determine it yet
> because the NSDMI for B::C::i hasn't been parsed yet.  This patch
> allows that determination to fail quietly in SFINAE context; we'll try
> again the next time it is needed.

Thanks.

Unfortunately it breaks the following testcase:

 ~ % cat asm-js.ii
struct A {
  void operator delete(void *, unsigned long);
};
struct B : A {
  virtual ~B();
};
struct C : B {};

 ~ % g++ -c asm-js.ii
asm-js.ii:7:8: error: no matching function for call to ‘C::operator delete(void*)’
 struct C : B {};
        ^
asm-js.ii:2:8: note: candidate: ‘static void A::operator delete(void*, long unsigned int)’
   void operator delete(void *, unsigned long);
        ^~~~~~~~
asm-js.ii:2:8: note:   candidate expects 2 arguments, 1 provided
diff mbox

Patch

diff --git a/gcc/cp/call.c b/gcc/cp/call.c
index fdd3731..4903119 100644
--- a/gcc/cp/call.c
+++ b/gcc/cp/call.c
@@ -916,7 +916,7 @@  build_aggr_conv (tree type, tree ctor, int flags, tsubst_flags_t complain)
       if (i < CONSTRUCTOR_NELTS (ctor))
 	val = CONSTRUCTOR_ELT (ctor, i)->value;
       else if (DECL_INITIAL (field))
-	val = get_nsdmi (field, /*ctor*/false);
+	val = get_nsdmi (field, /*ctor*/false, complain);
       else if (TREE_CODE (ftype) == REFERENCE_TYPE)
 	/* Value-initialization of reference is ill-formed.  */
 	return NULL;
diff --git a/gcc/cp/class.c b/gcc/cp/class.c
index 508570b..94738bd 100644
--- a/gcc/cp/class.c
+++ b/gcc/cp/class.c
@@ -8093,8 +8093,9 @@  resolve_address_of_overloaded_function (tree target_type,
 	  continue;
 
 	/* In C++17 we need the noexcept-qualifier to compare types.  */
-	if (flag_noexcept_type)
-	  maybe_instantiate_noexcept (fn);
+	if (flag_noexcept_type
+	    && !maybe_instantiate_noexcept (fn, complain))
+	  continue;
 
 	/* See if there's a match.  */
 	tree fntype = static_fn_type (fn);
@@ -8176,7 +8177,7 @@  resolve_address_of_overloaded_function (tree target_type,
 
 	  /* In C++17 we need the noexcept-qualifier to compare types.  */
 	  if (flag_noexcept_type)
-	    maybe_instantiate_noexcept (instantiation);
+	    maybe_instantiate_noexcept (instantiation, complain);
 
 	  /* See if there's a match.  */
 	  tree fntype = static_fn_type (instantiation);
diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h
index 115cdaf..3a0bd16 100644
--- a/gcc/cp/cp-tree.h
+++ b/gcc/cp/cp-tree.h
@@ -6297,7 +6297,7 @@  extern tree get_type_value			(tree);
 extern tree build_zero_init			(tree, tree, bool);
 extern tree build_value_init			(tree, tsubst_flags_t);
 extern tree build_value_init_noctor		(tree, tsubst_flags_t);
-extern tree get_nsdmi				(tree, bool);
+extern tree get_nsdmi				(tree, bool, tsubst_flags_t);
 extern tree build_offset_ref			(tree, tree, bool,
 						 tsubst_flags_t);
 extern tree throw_bad_array_new_length		(void);
@@ -6355,7 +6355,7 @@  extern bool trivial_fn_p			(tree);
 extern tree forward_parm			(tree);
 extern bool is_trivially_xible			(enum tree_code, tree, tree);
 extern bool is_xible				(enum tree_code, tree, tree);
-extern tree get_defaulted_eh_spec		(tree);
+extern tree get_defaulted_eh_spec		(tree, tsubst_flags_t = tf_warning_or_error);
 extern void after_nsdmi_defaulted_late_checks   (tree);
 extern bool maybe_explain_implicit_delete	(tree);
 extern void explain_implicit_non_constexpr	(tree);
@@ -6385,6 +6385,7 @@  extern tree cp_convert_range_for (tree, tree, tree, tree, unsigned int, bool);
 extern bool parsing_nsdmi (void);
 extern bool parsing_default_capturing_generic_lambda_in_template (void);
 extern void inject_this_parameter (tree, cp_cv_quals);
+extern location_t defarg_location (tree);
 
 /* in pt.c */
 extern bool check_template_shadow		(tree);
@@ -6448,7 +6449,7 @@  extern int more_specialized_fn			(tree, tree, int);
 extern void do_decl_instantiation		(tree, tree);
 extern void do_type_instantiation		(tree, tree, tsubst_flags_t);
 extern bool always_instantiate_p		(tree);
-extern void maybe_instantiate_noexcept		(tree);
+extern bool maybe_instantiate_noexcept		(tree, tsubst_flags_t = tf_warning_or_error);
 extern tree instantiate_decl			(tree, bool, bool);
 extern int comp_template_parms			(const_tree, const_tree);
 extern bool builtin_pack_fn_p			(tree);
@@ -7166,7 +7167,7 @@  extern tree split_nonconstant_init		(tree, tree);
 extern bool check_narrowing			(tree, tree, tsubst_flags_t);
 extern tree digest_init				(tree, tree, tsubst_flags_t);
 extern tree digest_init_flags			(tree, tree, int, tsubst_flags_t);
-extern tree digest_nsdmi_init		        (tree, tree);
+extern tree digest_nsdmi_init		        (tree, tree, tsubst_flags_t);
 extern tree build_scoped_ref			(tree, tree, tree *);
 extern tree build_x_arrow			(location_t, tree,
 						 tsubst_flags_t);
diff --git a/gcc/cp/decl2.c b/gcc/cp/decl2.c
index 2a52f8c..8187ab9 100644
--- a/gcc/cp/decl2.c
+++ b/gcc/cp/decl2.c
@@ -4988,8 +4988,9 @@  mark_used (tree decl, tsubst_flags_t complain)
   if (TREE_CODE (decl) == CONST_DECL)
     used_types_insert (DECL_CONTEXT (decl));
 
-  if (TREE_CODE (decl) == FUNCTION_DECL)
-    maybe_instantiate_noexcept (decl);
+  if (TREE_CODE (decl) == FUNCTION_DECL
+      && !maybe_instantiate_noexcept (decl, complain))
+    return false;
 
   if (TREE_CODE (decl) == FUNCTION_DECL
       && DECL_DELETED_FN (decl))
diff --git a/gcc/cp/error.c b/gcc/cp/error.c
index 2497c7f..31ca8fe 100644
--- a/gcc/cp/error.c
+++ b/gcc/cp/error.c
@@ -3046,6 +3046,8 @@  location_of (tree t)
 
   if (DECL_P (t))
     return DECL_SOURCE_LOCATION (t);
+  if (TREE_CODE (t) == DEFAULT_ARG)
+    return defarg_location (t);
   return EXPR_LOC_OR_LOC (t, input_location);
 }
 
diff --git a/gcc/cp/init.c b/gcc/cp/init.c
index 81804112..83e685c 100644
--- a/gcc/cp/init.c
+++ b/gcc/cp/init.c
@@ -536,7 +536,7 @@  perform_target_ctor (tree init)
 /* Return the non-static data initializer for FIELD_DECL MEMBER.  */
 
 tree
-get_nsdmi (tree member, bool in_ctor)
+get_nsdmi (tree member, bool in_ctor, tsubst_flags_t complain)
 {
   tree init;
   tree save_ccp = current_class_ptr;
@@ -554,50 +554,54 @@  get_nsdmi (tree member, bool in_ctor)
     {
       init = DECL_INITIAL (DECL_TI_TEMPLATE (member));
       if (TREE_CODE (init) == DEFAULT_ARG)
-	goto unparsed;
-
+	/* Unparsed.  */;
       /* Check recursive instantiation.  */
-      if (DECL_INSTANTIATING_NSDMI_P (member))
+      else if (DECL_INSTANTIATING_NSDMI_P (member))
 	{
-	  error ("recursive instantiation of non-static data member "
-		 "initializer for %qD", member);
+	  if (complain & tf_error)
+	    error ("recursive instantiation of default member "
+		   "initializer for %qD", member);
 	  init = error_mark_node;
 	}
       else
 	{
 	  DECL_INSTANTIATING_NSDMI_P (member) = 1;
-	  
+
 	  /* Do deferred instantiation of the NSDMI.  */
 	  init = (tsubst_copy_and_build
 		  (init, DECL_TI_ARGS (member),
-		   tf_warning_or_error, member, /*function_p=*/false,
+		   complain, member, /*function_p=*/false,
 		   /*integral_constant_expression_p=*/false));
-	  init = digest_nsdmi_init (member, init);
+	  init = digest_nsdmi_init (member, init, complain);
 	  
 	  DECL_INSTANTIATING_NSDMI_P (member) = 0;
 	}
     }
   else
+    init = DECL_INITIAL (member);
+
+  if (init && TREE_CODE (init) == DEFAULT_ARG)
     {
-      init = DECL_INITIAL (member);
-      if (init && TREE_CODE (init) == DEFAULT_ARG)
+      if (complain & tf_error)
 	{
-	unparsed:
-	  error ("constructor required before non-static data member "
-		 "for %qD has been parsed", member);
+	  error ("default member initializer for %qD required before the end "
+		 "of its enclosing class", member);
+	  inform (location_of (init), "defined here");
 	  DECL_INITIAL (member) = error_mark_node;
-	  init = error_mark_node;
 	}
-      /* Strip redundant TARGET_EXPR so we don't need to remap it, and
-	 so the aggregate init code below will see a CONSTRUCTOR.  */
-      bool simple_target = (init && SIMPLE_TARGET_EXPR_P (init));
-      if (simple_target)
-	init = TARGET_EXPR_INITIAL (init);
-      init = break_out_target_exprs (init);
-      if (simple_target && TREE_CODE (init) != CONSTRUCTOR)
-	/* Now put it back so C++17 copy elision works.  */
-	init = get_target_expr (init);
+      init = error_mark_node;
     }
+
+  /* Strip redundant TARGET_EXPR so we don't need to remap it, and
+     so the aggregate init code below will see a CONSTRUCTOR.  */
+  bool simple_target = (init && SIMPLE_TARGET_EXPR_P (init));
+  if (simple_target)
+    init = TARGET_EXPR_INITIAL (init);
+  init = break_out_target_exprs (init);
+  if (simple_target && TREE_CODE (init) != CONSTRUCTOR)
+    /* Now put it back so C++17 copy elision works.  */
+    init = get_target_expr (init);
+
   current_class_ptr = save_ccp;
   current_class_ref = save_ccr;
   return init;
@@ -644,7 +648,7 @@  perform_member_init (tree member, tree init)
   /* Use the non-static data member initializer if there was no
      mem-initializer for this field.  */
   if (init == NULL_TREE)
-    init = get_nsdmi (member, /*ctor*/true);
+    init = get_nsdmi (member, /*ctor*/true, tf_warning_or_error);
 
   if (init == error_mark_node)
     return;
diff --git a/gcc/cp/method.c b/gcc/cp/method.c
index 8b07f52..bff9605 100644
--- a/gcc/cp/method.c
+++ b/gcc/cp/method.c
@@ -1357,7 +1357,7 @@  walk_field_subobs (tree fields, tree fnname, special_function_kind sfk,
 		 default constructor is noexcept(false).  */
 	      if (spec_p)
 		{
-		  tree nsdmi = get_nsdmi (field, /*ctor*/false);
+		  tree nsdmi = get_nsdmi (field, /*ctor*/false, complain);
 		  if (!expr_noexcept_p (nsdmi, complain))
 		    *spec_p = noexcept_false_spec;
 		}
@@ -1660,6 +1660,10 @@  synthesized_method_walk (tree ctype, special_function_kind sfk, bool const_p,
     flags |= LOOKUP_DEFAULTED;
 
   tsubst_flags_t complain = diag ? tf_warning_or_error : tf_none;
+  if (diag && spec_p)
+    /* We're in get_defaulted_eh_spec; we don't actually want any walking
+       diagnostics, we just want complain set.  */
+    diag = false;
   int quals = const_p ? TYPE_QUAL_CONST : TYPE_UNQUALIFIED;
 
   for (binfo = TYPE_BINFO (ctype), i = 0;
@@ -1749,7 +1753,7 @@  synthesized_method_walk (tree ctype, special_function_kind sfk, bool const_p,
    needed.  Return what it should be.  */
 
 tree
-get_defaulted_eh_spec (tree decl)
+get_defaulted_eh_spec (tree decl, tsubst_flags_t complain)
 {
   if (DECL_CLONED_FUNCTION_P (decl))
     decl = DECL_CLONED_FUNCTION (decl);
@@ -1759,8 +1763,9 @@  get_defaulted_eh_spec (tree decl)
   tree parm_type = TREE_VALUE (parms);
   bool const_p = CP_TYPE_CONST_P (non_reference (parm_type));
   tree spec = empty_except_spec;
+  bool diag = !DECL_DELETED_FN (decl) && (complain & tf_error);
   synthesized_method_walk (ctype, sfk, const_p, &spec, NULL, NULL,
-			   NULL, false, DECL_INHERITED_CTOR (decl),
+			   NULL, diag, DECL_INHERITED_CTOR (decl),
 			   parms);
   return spec;
 }
@@ -2173,6 +2178,12 @@  defaulted_late_check (tree fn)
 		"does not match expected signature %qD", implicit_fn);
     }
 
+  if (DECL_DELETED_FN (implicit_fn))
+    {
+      DECL_DELETED_FN (fn) = 1;
+      return;
+    }
+
   /* 8.4.2/2: An explicitly-defaulted function (...) may have an explicit
      exception-specification only if it is compatible (15.4) with the 
      exception-specification on the implicit declaration.  If a function
@@ -2231,9 +2242,6 @@  defaulted_late_check (tree fn)
 	}
       DECL_DECLARED_CONSTEXPR_P (fn) = false;
     }
-
-  if (DECL_DELETED_FN (implicit_fn))
-    DECL_DELETED_FN (fn) = 1;
 }
 
 /* OK, we've parsed the NSDMI for class T, now we can check any explicit
diff --git a/gcc/cp/parser.c b/gcc/cp/parser.c
index 23bd278..9458f2f 100644
--- a/gcc/cp/parser.c
+++ b/gcc/cp/parser.c
@@ -27431,7 +27431,7 @@  cp_parser_late_parse_one_default_arg (cp_parser *parser, tree decl,
       else if (maybe_reject_flexarray_init (decl, parsed_arg))
 	parsed_arg = error_mark_node;
       else
-	parsed_arg = digest_nsdmi_init (decl, parsed_arg);
+	parsed_arg = digest_nsdmi_init (decl, parsed_arg, tf_warning_or_error);
     }
 
   /* If the token stream has not been completely used up, then
@@ -28681,6 +28681,17 @@  cp_parser_cache_defarg (cp_parser *parser, bool nsdmi)
   return default_argument;
 }
 
+/* A location to use for diagnostics about an unparsed DEFAULT_ARG.  */
+
+location_t
+defarg_location (tree default_argument)
+{
+  cp_token_cache *tokens = DEFARG_TOKENS (default_argument);
+  location_t start = tokens->first->location;
+  location_t end = tokens->last->location;
+  return make_location (start, start, end);
+}
+
 /* Begin parsing tentatively.  We always save tokens while parsing
    tentatively so that if the tentative parsing fails we can restore the
    tokens.  */
diff --git a/gcc/cp/pt.c b/gcc/cp/pt.c
index bd61438..3d6f4b5 100644
--- a/gcc/cp/pt.c
+++ b/gcc/cp/pt.c
@@ -22493,16 +22493,17 @@  always_instantiate_p (tree decl)
 }
 
 /* If FN has a noexcept-specifier that hasn't been instantiated yet,
-   instantiate it now, modifying TREE_TYPE (fn).  */
+   instantiate it now, modifying TREE_TYPE (fn).  Returns false on
+   error, true otherwise.  */
 
-void
-maybe_instantiate_noexcept (tree fn)
+bool
+maybe_instantiate_noexcept (tree fn, tsubst_flags_t complain)
 {
   tree fntype, spec, noex, clone;
 
   /* Don't instantiate a noexcept-specification from template context.  */
   if (processing_template_decl)
-    return;
+    return true;
 
   if (DECL_CLONED_FUNCTION_P (fn))
     fn = DECL_CLONED_FUNCTION (fn);
@@ -22510,7 +22511,7 @@  maybe_instantiate_noexcept (tree fn)
   spec = TYPE_RAISES_EXCEPTIONS (fntype);
 
   if (!spec || !TREE_PURPOSE (spec))
-    return;
+    return true;
 
   noex = TREE_PURPOSE (spec);
 
@@ -22519,7 +22520,7 @@  maybe_instantiate_noexcept (tree fn)
       static hash_set<tree>* fns = new hash_set<tree>;
       bool added = false;
       if (DEFERRED_NOEXCEPT_PATTERN (noex) == NULL_TREE)
-	spec = get_defaulted_eh_spec (fn);
+	spec = get_defaulted_eh_spec (fn, complain);
       else if (!(added = !fns->add (fn)))
 	{
 	  /* If hash_set::add returns true, the element was already there.  */
@@ -22553,6 +22554,9 @@  maybe_instantiate_noexcept (tree fn)
       if (added)
 	fns->remove (fn);
 
+      if (spec == error_mark_node)
+	return false;
+
       TREE_TYPE (fn) = build_exception_variant (fntype, spec);
     }
 
@@ -22563,6 +22567,8 @@  maybe_instantiate_noexcept (tree fn)
       else
 	TREE_TYPE (clone) = build_exception_variant (TREE_TYPE (clone), spec);
     }
+
+  return true;
 }
 
 /* Produce the definition of D, a _DECL generated from a template.  If
diff --git a/gcc/cp/typeck2.c b/gcc/cp/typeck2.c
index 430ba30..06c079e 100644
--- a/gcc/cp/typeck2.c
+++ b/gcc/cp/typeck2.c
@@ -1182,7 +1182,7 @@  digest_init_flags (tree type, tree init, int flags, tsubst_flags_t complain)
 
 /* Process the initializer INIT for an NSDMI DECL (a FIELD_DECL).  */
 tree
-digest_nsdmi_init (tree decl, tree init)
+digest_nsdmi_init (tree decl, tree init, tsubst_flags_t complain)
 {
   gcc_assert (TREE_CODE (decl) == FIELD_DECL);
 
@@ -1192,8 +1192,8 @@  digest_nsdmi_init (tree decl, tree init)
     flags = LOOKUP_NORMAL;
   if (BRACE_ENCLOSED_INITIALIZER_P (init)
       && CP_AGGREGATE_TYPE_P (type))
-    init = reshape_init (type, init, tf_warning_or_error);
-  init = digest_init_flags (type, init, flags, tf_warning_or_error);
+    init = reshape_init (type, init, complain);
+  init = digest_init_flags (type, init, flags, complain);
   if (TREE_CODE (init) == TARGET_EXPR)
     /* This represents the whole initialization.  */
     TARGET_EXPR_DIRECT_INIT_P (init) = true;
@@ -1427,7 +1427,7 @@  process_init_constructor_record (tree type, tree init,
 	      goto restart;
 	    }
 	  /* C++14 aggregate NSDMI.  */
-	  next = get_nsdmi (field, /*ctor*/false);
+	  next = get_nsdmi (field, /*ctor*/false, complain);
 	}
       else if (type_build_ctor_call (TREE_TYPE (field)))
 	{
@@ -1525,7 +1525,8 @@  process_init_constructor_union (tree type, tree init,
 	    {
 	      CONSTRUCTOR_APPEND_ELT (CONSTRUCTOR_ELTS (init),
 				      field,
-				      get_nsdmi (field, /*in_ctor=*/false));
+				      get_nsdmi (field, /*in_ctor=*/false,
+						 complain));
 	      break;
 	    }
 	}
diff --git a/gcc/testsuite/g++.dg/cpp0x/nsdmi-defer6.C b/gcc/testsuite/g++.dg/cpp0x/nsdmi-defer6.C
index 0f06343..056d16d 100644
--- a/gcc/testsuite/g++.dg/cpp0x/nsdmi-defer6.C
+++ b/gcc/testsuite/g++.dg/cpp0x/nsdmi-defer6.C
@@ -2,7 +2,7 @@ 
 
 struct A
 {
-  int i = (A(), 42);		// { dg-error "constructor required" }
+  int i = (A(), 42);		// { dg-error "" }
 };
 
 A a;
diff --git a/gcc/testsuite/g++.dg/cpp0x/nsdmi-template14.C b/gcc/testsuite/g++.dg/cpp0x/nsdmi-template14.C
index 1a00ec0..a885a24 100644
--- a/gcc/testsuite/g++.dg/cpp0x/nsdmi-template14.C
+++ b/gcc/testsuite/g++.dg/cpp0x/nsdmi-template14.C
@@ -3,14 +3,14 @@ 
 
 template<int> struct A
 {
-  int i = (A<0>(), 0); // { dg-error "recursive instantiation of non-static data" }
+  int i = (A<0>(), 0); // { dg-error "recursive instantiation of default" }
 };
 
 A<0> a;
 
 template<int N> struct B
 {
-  B* p = new B<N>; // { dg-error "recursive instantiation of non-static data" }
+  B* p = new B<N>; // { dg-error "recursive instantiation of default" }
 };
 
 B<1> x;
diff --git a/gcc/testsuite/g++.dg/cpp0x/nsdmi10.C b/gcc/testsuite/g++.dg/cpp0x/nsdmi10.C
index 56f9ff0..d8588b7 100644
--- a/gcc/testsuite/g++.dg/cpp0x/nsdmi10.C
+++ b/gcc/testsuite/g++.dg/cpp0x/nsdmi10.C
@@ -6,7 +6,7 @@  struct A1 {
     int y1 = 1;
   };
 
-  A1(const B1& opts = B1()) {}  // { dg-error "constructor" }
+  A1(const B1& opts = B1()) {}  // { dg-error "default member initializer" }
 };
 
 struct A2 {
@@ -14,5 +14,5 @@  struct A2 {
     int x2, y2 = 1;
   };
 
-  A2(const B2& opts = B2()) {}  // { dg-error "constructor" }
+  A2(const B2& opts = B2()) {}  // { dg-error "default member initializer" }
 };
diff --git a/gcc/testsuite/g++.dg/cpp0x/sfinae59.C b/gcc/testsuite/g++.dg/cpp0x/sfinae59.C
new file mode 100644
index 0000000..d1c730b
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp0x/sfinae59.C
@@ -0,0 +1,19 @@ 
+// PR c++/81359
+// { dg-do compile { target c++11 } }
+
+template<typename _Tp, typename = decltype(_Tp())>
+static int test(int);
+
+template<typename>
+static void test(...);
+
+template <class T, class = decltype(test<T>(0))>
+struct A { };
+
+struct B
+{
+  struct C {
+    int i = 0;
+  };
+  A<C> a;
+};