]> granicus.if.org Git - clang/commitdiff
Record some basic information about bad conversion sequences. Use that
authorJohn McCall <rjmccall@apple.com>
Wed, 13 Jan 2010 09:16:55 +0000 (09:16 +0000)
committerJohn McCall <rjmccall@apple.com>
Wed, 13 Jan 2010 09:16:55 +0000 (09:16 +0000)
information to feed diagnostics instead of regenerating it.  Much room for
improvement here, but fixes some unfortunate problems reporting on method calls.

git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@93316 91177308-0d34-0410-b5e6-96231b3b80d8

lib/Sema/SemaDeclCXX.cpp
lib/Sema/SemaExprCXX.cpp
lib/Sema/SemaOverload.cpp
lib/Sema/SemaOverload.h
test/SemaCXX/overload-call.cpp
test/SemaCXX/overload-member-call.cpp

index e10398af85fe26268d3a76cd28e63f6073f24ede..edeb7c13e1098fd2c8e3393e84568110cc614485 100644 (file)
@@ -4389,7 +4389,10 @@ Sema::CheckReferenceInit(Expr *&Init, QualType DeclType,
     = CompareReferenceRelationship(DeclLoc, T1, T2, DerivedToBase);
 
   // Most paths end in a failed conversion.
-  if (ICS) ICS->setBad();
+  if (ICS) {
+    ICS->setBad();
+    ICS->Bad.init(BadConversionSequence::no_conversion, Init, DeclType);
+  }
 
   // C++ [dcl.init.ref]p5:
   //   A reference to type "cv1 T1" is initialized by an expression
index d6947986a45507a6fe32f0aa612af1feaac4409b..d10e11fdb4a348246b3a9e0f0abaaebc7cbbb2e1 100644 (file)
@@ -1076,6 +1076,7 @@ Sema::PerformImplicitConversion(Expr *&From, QualType ToType,
                                 bool Elidable,
                                 ImplicitConversionSequence& ICS) {
   ICS.setBad();
+  ICS.Bad.init(BadConversionSequence::no_conversion, From, ToType);
   if (Elidable && getLangOptions().CPlusPlus0x) {
     ICS = TryImplicitConversion(From, ToType,
                                 /*SuppressUserConversions=*/false,
@@ -1942,7 +1943,7 @@ QualType Sema::FindCompositePointerType(Expr *&E1, Expr *&E2) {
 
   ImplicitConversionSequence E1ToC2, E2ToC2;
   E1ToC2.setBad();
-  E2ToC2.setBad();
+  E2ToC2.setBad();  
   if (Context.getCanonicalType(Composite1) !=
       Context.getCanonicalType(Composite2)) {
     E1ToC2 = TryImplicitConversion(E1, Composite2,
index 4f299ff5b0ed503b18c716b421cb3cdd884dd745..ffcf8d4609ac85cc04f7ddaea532364866d62f53 100644 (file)
@@ -489,8 +489,10 @@ Sema::TryImplicitConversion(Expr* From, QualType ToType,
     //   of a class copy-initialization, or by 13.3.1.4, 13.3.1.5, or
     //   13.3.1.6 in all cases, only standard conversion sequences and
     //   ellipsis conversion sequences are allowed.
-    if (SuppressUserConversions && ICS.isUserDefined())
+    if (SuppressUserConversions && ICS.isUserDefined()) {
       ICS.setBad();
+      ICS.Bad.init(BadConversionSequence::suppressed_user, From, ToType);
+    }
   } else if (UserDefResult == OR_Ambiguous) {
     ICS.setAmbiguous();
     ICS.Ambiguous.setFromType(From->getType());
@@ -501,6 +503,7 @@ Sema::TryImplicitConversion(Expr* From, QualType ToType,
         ICS.Ambiguous.addConversion(Cand->Function);
   } else {
     ICS.setBad();
+    ICS.Bad.init(BadConversionSequence::no_conversion, From, ToType);
   }
 
   return ICS;
@@ -2129,6 +2132,7 @@ Sema::TryCopyInitialization(Expr *From, QualType ToType,
                             bool InOverloadResolution) {
   if (ToType->isReferenceType()) {
     ImplicitConversionSequence ICS;
+    ICS.Bad.init(BadConversionSequence::no_conversion, From, ToType);
     CheckReferenceInit(From, ToType,
                        /*FIXME:*/From->getLocStart(),
                        SuppressUserConversions,
@@ -2223,8 +2227,10 @@ Sema::TryObjectArgumentInitialization(QualType FromType,
   QualType FromTypeCanon = Context.getCanonicalType(FromType);
   if (ImplicitParamType.getCVRQualifiers() 
                                     != FromTypeCanon.getLocalCVRQualifiers() &&
-      !ImplicitParamType.isAtLeastAsQualifiedAs(FromTypeCanon))
+      !ImplicitParamType.isAtLeastAsQualifiedAs(FromTypeCanon)) {
+    ICS.Bad.init(BadConversionSequence::bad_qualifiers, FromType, ImplicitParamType);
     return ICS;
+  }
 
   // Check that we have either the same type or a derived type. It
   // affects the conversion rank.
@@ -2233,8 +2239,10 @@ Sema::TryObjectArgumentInitialization(QualType FromType,
     ICS.Standard.Second = ICK_Identity;
   else if (IsDerivedFrom(FromType, ClassType))
     ICS.Standard.Second = ICK_Derived_To_Base;
-  else
+  else {
+    ICS.Bad.init(BadConversionSequence::unrelated_class, FromType, ImplicitParamType);
     return ICS;
+  }
 
   // Success. Mark this as a reference binding.
   ICS.setStandard();
@@ -2385,6 +2393,7 @@ Sema::AddOverloadCandidate(FunctionDecl *Function,
   if ((NumArgs + (PartialOverloading && NumArgs)) > NumArgsInProto && 
       !Proto->isVariadic()) {
     Candidate.Viable = false;
+    Candidate.FailureKind = ovl_fail_too_many_arguments;
     return;
   }
 
@@ -2397,6 +2406,7 @@ Sema::AddOverloadCandidate(FunctionDecl *Function,
   if (NumArgs < MinRequiredArgs && !PartialOverloading) {
     // Not enough arguments.
     Candidate.Viable = false;
+    Candidate.FailureKind = ovl_fail_too_few_arguments;
     return;
   }
 
@@ -2416,6 +2426,7 @@ Sema::AddOverloadCandidate(FunctionDecl *Function,
                                 /*InOverloadResolution=*/true);
       if (Candidate.Conversions[ArgIdx].isBad()) {
         Candidate.Viable = false;
+        Candidate.FailureKind = ovl_fail_bad_conversion;
         break;
       }
     } else {
@@ -2532,6 +2543,7 @@ Sema::AddMethodCandidate(CXXMethodDecl *Method, CXXRecordDecl *ActingContext,
   // list (8.3.5).
   if (NumArgs > NumArgsInProto && !Proto->isVariadic()) {
     Candidate.Viable = false;
+    Candidate.FailureKind = ovl_fail_too_many_arguments;
     return;
   }
 
@@ -2544,6 +2556,7 @@ Sema::AddMethodCandidate(CXXMethodDecl *Method, CXXRecordDecl *ActingContext,
   if (NumArgs < MinRequiredArgs) {
     // Not enough arguments.
     Candidate.Viable = false;
+    Candidate.FailureKind = ovl_fail_too_few_arguments;
     return;
   }
 
@@ -2560,6 +2573,7 @@ Sema::AddMethodCandidate(CXXMethodDecl *Method, CXXRecordDecl *ActingContext,
       = TryObjectArgumentInitialization(ObjectType, Method, ActingContext);
     if (Candidate.Conversions[0].isBad()) {
       Candidate.Viable = false;
+      Candidate.FailureKind = ovl_fail_bad_conversion;
       return;
     }
   }
@@ -2579,6 +2593,7 @@ Sema::AddMethodCandidate(CXXMethodDecl *Method, CXXRecordDecl *ActingContext,
                                 /*InOverloadResolution=*/true);
       if (Candidate.Conversions[ArgIdx + 1].isBad()) {
         Candidate.Viable = false;
+        Candidate.FailureKind = ovl_fail_bad_conversion;
         break;
       }
     } else {
@@ -2670,6 +2685,7 @@ Sema::AddTemplateOverloadCandidate(FunctionTemplateDecl *FunctionTemplate,
     OverloadCandidate &Candidate = CandidateSet.back();
     Candidate.Function = FunctionTemplate->getTemplatedDecl();
     Candidate.Viable = false;
+    Candidate.FailureKind = ovl_fail_bad_deduction;
     Candidate.IsSurrogate = false;
     Candidate.IgnoreObjectArgument = false;
     return;
@@ -2726,6 +2742,7 @@ Sema::AddConversionCandidate(CXXConversionDecl *Conversion,
     Candidate.Conversions[0].Standard.Second = ICK_Identity;
   if (Candidate.Conversions[0].isBad()) {
     Candidate.Viable = false;
+    Candidate.FailureKind = ovl_fail_bad_conversion;
     return;
   }
   
@@ -2737,6 +2754,7 @@ Sema::AddConversionCandidate(CXXConversionDecl *Conversion,
   QualType ToCanon = Context.getCanonicalType(ToType).getUnqualifiedType();
   if (FromCanon == ToCanon || IsDerivedFrom(FromCanon, ToCanon)) {
     Candidate.Viable = false;
+    Candidate.FailureKind = ovl_fail_bad_conversion;
     return;
   }
   
@@ -2774,6 +2792,7 @@ Sema::AddConversionCandidate(CXXConversionDecl *Conversion,
 
   case ImplicitConversionSequence::BadConversion:
     Candidate.Viable = false;
+    Candidate.FailureKind = ovl_fail_bad_conversion;
     break;
 
   default:
@@ -2847,6 +2866,7 @@ void Sema::AddSurrogateCandidate(CXXConversionDecl *Conversion,
     = TryObjectArgumentInitialization(ObjectType, Conversion, ActingContext);
   if (ObjectInit.isBad()) {
     Candidate.Viable = false;
+    Candidate.FailureKind = ovl_fail_bad_conversion;
     return;
   }
 
@@ -2869,6 +2889,7 @@ void Sema::AddSurrogateCandidate(CXXConversionDecl *Conversion,
   // list (8.3.5).
   if (NumArgs > NumArgsInProto && !Proto->isVariadic()) {
     Candidate.Viable = false;
+    Candidate.FailureKind = ovl_fail_too_many_arguments;
     return;
   }
 
@@ -2877,6 +2898,7 @@ void Sema::AddSurrogateCandidate(CXXConversionDecl *Conversion,
   if (NumArgs < NumArgsInProto) {
     // Not enough arguments.
     Candidate.Viable = false;
+    Candidate.FailureKind = ovl_fail_too_few_arguments;
     return;
   }
 
@@ -2896,6 +2918,7 @@ void Sema::AddSurrogateCandidate(CXXConversionDecl *Conversion,
                                 /*InOverloadResolution=*/false);
       if (Candidate.Conversions[ArgIdx + 1].isBad()) {
         Candidate.Viable = false;
+        Candidate.FailureKind = ovl_fail_bad_conversion;
         break;
       }
     } else {
@@ -3038,6 +3061,7 @@ void Sema::AddBuiltinCandidate(QualType ResultTy, QualType *ParamTys,
     }
     if (Candidate.Conversions[ArgIdx].isBad()) {
       Candidate.Viable = false;
+      Candidate.FailureKind = ovl_fail_bad_conversion;
       break;
     }
   }
@@ -4336,36 +4360,69 @@ void Sema::DiagnoseAmbiguousConversion(const ImplicitConversionSequence &ICS,
 
 namespace {
 
-void DiagnoseBadConversion(Sema &S, OverloadCandidate *Cand, unsigned I,
-                           Expr **Args, unsigned NumArgs) {
+void DiagnoseBadConversion(Sema &S, OverloadCandidate *Cand, unsigned I) {
+  const ImplicitConversionSequence &Conv = Cand->Conversions[I];
+  assert(Conv.isBad());
   assert(Cand->Function && "for now, candidate must be a function");
   FunctionDecl *Fn = Cand->Function;
 
   // There's a conversion slot for the object argument if this is a
   // non-constructor method.  Note that 'I' corresponds the
   // conversion-slot index.
+  bool isObjectArgument = false;
   if (isa<CXXMethodDecl>(Fn) && !isa<CXXConstructorDecl>(Fn)) {
-    // FIXME: talk usefully about bad conversions for object arguments.
-    if (I == 0) return S.NoteOverloadCandidate(Fn);
-    else I--;
+    if (I == 0)
+      isObjectArgument = true;
+    else
+      I--;
   }
 
-  // FIXME: can we have a bad conversion on an ellipsis parameter?
-  assert(I < NumArgs && "index exceeds number of formal arguments");
-  assert(I < Fn->getType()->getAs<FunctionProtoType>()->getNumArgs() &&
-         "index exceeds number of formal parameters");
-
   std::string FnDesc;
   OverloadCandidateKind FnKind = ClassifyOverloadCandidate(S, Fn, FnDesc);
 
-  QualType FromTy = Args[I]->getType();
-  QualType ToTy = Fn->getType()->getAs<FunctionProtoType>()->getArgType(I);
+  Expr *FromExpr = Conv.Bad.FromExpr;
+  QualType FromTy = Conv.Bad.getFromType();
+  QualType ToTy = Conv.Bad.getToType();
 
   // TODO: specialize based on the kind of mismatch
   S.Diag(Fn->getLocation(), diag::note_ovl_candidate_bad_conv)
     << (unsigned) FnKind << FnDesc
-    << Args[I]->getSourceRange() << FromTy << ToTy
-    << I+1;
+    << (FromExpr ? FromExpr->getSourceRange() : SourceRange())
+    << FromTy << ToTy << I+1;
+}
+
+void DiagnoseArityMismatch(Sema &S, OverloadCandidate *Cand,
+                           unsigned NumFormalArgs) {
+  // TODO: treat calls to a missing default constructor as a special case
+
+  FunctionDecl *Fn = Cand->Function;
+  const FunctionProtoType *FnTy = Fn->getType()->getAs<FunctionProtoType>();
+
+  unsigned MinParams = Fn->getMinRequiredArguments();
+  
+  // at least / at most / exactly
+  unsigned mode, modeCount;
+  if (NumFormalArgs < MinParams) {
+    assert(Cand->FailureKind == ovl_fail_too_few_arguments);
+    if (MinParams != FnTy->getNumArgs() || FnTy->isVariadic())
+      mode = 0; // "at least"
+    else
+      mode = 2; // "exactly"
+    modeCount = MinParams;
+  } else {
+    assert(Cand->FailureKind == ovl_fail_too_many_arguments);
+    if (MinParams != FnTy->getNumArgs())
+      mode = 1; // "at most"
+    else
+      mode = 2; // "exactly"
+    modeCount = FnTy->getNumArgs();
+  }
+
+  std::string Description;
+  OverloadCandidateKind FnKind = ClassifyOverloadCandidate(S, Fn, Description);
+
+  S.Diag(Fn->getLocation(), diag::note_ovl_candidate_arity)
+    << (unsigned) FnKind << Description << mode << modeCount << NumFormalArgs;
 }
 
 void NoteFunctionCandidate(Sema &S, OverloadCandidate *Cand,
@@ -4388,52 +4445,24 @@ void NoteFunctionCandidate(Sema &S, OverloadCandidate *Cand,
     return;
   }
 
-  // Diagnose arity mismatches.
-  // TODO: treat calls to a missing default constructor as a special case
-  unsigned NumFormalArgs = NumArgs;
-  if (isa<CXXMethodDecl>(Fn) && !isa<CXXConstructorDecl>(Fn))
-    NumFormalArgs--;
-  const FunctionProtoType *FnTy = Fn->getType()->getAs<FunctionProtoType>();
-  unsigned MinParams = Fn->getMinRequiredArguments();
-  if (NumFormalArgs < MinParams ||
-      (NumFormalArgs > FnTy->getNumArgs() && !FnTy->isVariadic())) {
-    std::string Description;
-    OverloadCandidateKind FnKind = ClassifyOverloadCandidate(S, Fn, Description);
-
-    // at least / at most / exactly
-    unsigned mode, modeCount;
-    if (NumFormalArgs < MinParams) {
-      if (MinParams != FnTy->getNumArgs())
-        mode = 0; // "at least"
-      else
-        mode = 2; // "exactly"
-      modeCount = MinParams;
-    } else {
-      if (MinParams != FnTy->getNumArgs())
-        mode = 1; // "at most"
-      else
-        mode = 2; // "exactly"
-      modeCount = FnTy->getNumArgs();
-    }
+  switch (Cand->FailureKind) {
+  case ovl_fail_too_many_arguments:
+  case ovl_fail_too_few_arguments:
+    return DiagnoseArityMismatch(S, Cand, NumArgs);
 
-    S.Diag(Fn->getLocation(), diag::note_ovl_candidate_arity)
-      << (unsigned) FnKind << Description << mode << modeCount << NumFormalArgs;
-    return;
-  }
-
-  // Look for bad conversions.
-  if (!Cand->Conversions.empty()) {
-    for (unsigned I = 0, N = Cand->Conversions.size(); I != N; ++I) {
-      if (!Cand->Conversions[I].isBad())
-        continue;
+  case ovl_fail_bad_deduction:
+    return S.NoteOverloadCandidate(Fn);
 
-      DiagnoseBadConversion(S, Cand, I, Args, NumArgs);
-      return;
-    }
+  case ovl_fail_bad_conversion:
+    for (unsigned I = 0, N = Cand->Conversions.size(); I != N; ++I)
+      if (Cand->Conversions[I].isBad())
+        return DiagnoseBadConversion(S, Cand, I);
+    
+    // FIXME: this currently happens when we're called from SemaInit
+    // when user-conversion overload fails.  Figure out how to handle
+    // those conditions and diagnose them well.
+    return S.NoteOverloadCandidate(Fn);
   }
-
-  // Give up and give the generic message.
-  S.NoteOverloadCandidate(Fn);
 }
 
 void NoteSurrogateCandidate(Sema &S, OverloadCandidate *Cand) {
index 78fa05b24cf1bba6c65420170dc7aab6b179fbbc..20add007f7c04a533f8e48203beb3c76f93839ca 100644 (file)
@@ -16,6 +16,7 @@
 #define LLVM_CLANG_SEMA_OVERLOAD_H
 
 #include "clang/AST/Decl.h"
+#include "clang/AST/Expr.h"
 #include "clang/AST/Type.h"
 #include "llvm/ADT/SmallPtrSet.h"
 #include "llvm/ADT/SmallVector.h"
@@ -241,6 +242,51 @@ namespace clang {
     void copyFrom(const AmbiguousConversionSequence &);
   };
 
+  /// BadConversionSequence - Records information about an invalid
+  /// conversion sequence.
+  struct BadConversionSequence {
+    enum FailureKind {
+      no_conversion,
+      unrelated_class,
+      suppressed_user,
+      bad_qualifiers
+    };
+
+    // This can be null, e.g. for implicit object arguments.
+    Expr *FromExpr;
+
+    FailureKind Kind;
+
+  private:
+    // The type we're converting from (an opaque QualType).
+    void *FromTy;
+
+    // The type we're converting to (an opaque QualType).
+    void *ToTy;
+
+  public:
+    void init(FailureKind K, Expr *From, QualType To) {
+      init(K, From->getType(), To);
+      FromExpr = From;
+    }
+    void init(FailureKind K, QualType From, QualType To) {
+      Kind = K;
+      FromExpr = 0;
+      setFromType(From);
+      setToType(To);
+    }
+
+    QualType getFromType() const { return QualType::getFromOpaquePtr(FromTy); }
+    QualType getToType() const { return QualType::getFromOpaquePtr(ToTy); }
+
+    void setFromExpr(Expr *E) {
+      FromExpr = E;
+      setFromType(E->getType());
+    }
+    void setFromType(QualType T) { FromTy = T.getAsOpaquePtr(); }
+    void setToType(QualType T) { ToTy = T.getAsOpaquePtr(); }
+  };
+
   /// ImplicitConversionSequence - Represents an implicit conversion
   /// sequence, which may be a standard conversion sequence
   /// (C++ 13.3.3.1.1), user-defined conversion sequence (C++ 13.3.3.1.2),
@@ -280,6 +326,10 @@ namespace clang {
       /// When ConversionKind == AmbiguousConversion, provides the
       /// details of the ambiguous conversion.
       AmbiguousConversionSequence Ambiguous;
+
+      /// When ConversionKind == BadConversion, provides the details
+      /// of the bad conversion.
+      BadConversionSequence Bad;
     };
 
     ImplicitConversionSequence() : ConversionKind(BadConversion) {}
@@ -294,7 +344,7 @@ namespace clang {
       case UserDefinedConversion: UserDefined = Other.UserDefined; break;
       case AmbiguousConversion: Ambiguous.copyFrom(Other.Ambiguous); break;
       case EllipsisConversion: break;
-      case BadConversion: break;
+      case BadConversion: Bad = Other.Bad; break;
       }
     }
 
@@ -336,6 +386,13 @@ namespace clang {
     void DebugPrint() const;
   };
 
+  enum OverloadFailureKind {
+    ovl_fail_too_many_arguments,
+    ovl_fail_too_few_arguments,
+    ovl_fail_bad_conversion,
+    ovl_fail_bad_deduction
+  };
+
   /// OverloadCandidate - A single candidate in an overload set (C++ 13.3).
   struct OverloadCandidate {
     /// Function - The actual function that this candidate
@@ -376,6 +433,10 @@ namespace clang {
     /// object argument.
     bool IgnoreObjectArgument;
 
+    /// FailureKind - The reason why this candidate is not viable.
+    /// Actually an OverloadFailureKind.
+    unsigned char FailureKind;
+
     /// FinalConversion - For a conversion function (where Function is
     /// a CXXConversionDecl), the standard conversion that occurs
     /// after the call to the overload candidate to convert the result
index fbd26b2fd838a1bd592cc02b93f48bd84544f4d2..0a2508d4b8067e95360d0578c6fb3b84b886d8f5 100644 (file)
@@ -304,10 +304,16 @@ namespace PR5756 {
 
 // Tests the exact text used to note the candidates
 namespace test1 {
-  template <class T> void foo(T t, unsigned N); // expected-note {{candidate function [with T = int]}}
-  void foo(int n, char N); // expected-note {{candidate function}} 
+  template <class T> void foo(T t, unsigned N); // expected-note {{candidate function [with T = int] not viable: no known conversion from 'char const [6]' to 'unsigned int' for argument 2}}
+  void foo(int n, char N); // expected-note {{candidate function not viable: no known conversion from 'char const [6]' to 'char' for argument 2}} 
+  void foo(int n); // expected-note {{candidate function not viable: requires 1 argument, but 2 were provided}}
+  void foo(unsigned n = 10); // expected-note {{candidate function not viable: requires at most 1 argument, but 2 were provided}}
+  void foo(int n, const char *s, int t); // expected-note {{candidate function not viable: requires 3 arguments, but 2 were provided}}
+  void foo(int n, const char *s, int t, ...); // expected-note {{candidate function not viable: requires at least 3 arguments, but 2 were provided}}
+  void foo(int n, const char *s, int t, int u = 0); // expected-note {{candidate function not viable: requires at least 3 arguments, but 2 were provided}}
 
   void test() {
     foo(4, "hello"); //expected-error {{no matching function for call to 'foo'}}
   }
 }
+
index 4bb3ff3a54cd6b4ad9a5a1c65fe2fe2330f983cd..8eb189850b66e98b9966db226084a3d8aac5f54c 100644 (file)
@@ -66,3 +66,22 @@ void test_X2(X2 *x2p, const X2 *cx2p) {
   int &ir = x2p->member();
   float &fr = cx2p->member();
 }
+
+// Tests the exact text used to note the candidates
+namespace test1 {
+  class A {
+    template <class T> void foo(T t, unsigned N); // expected-note {{candidate function [with T = int] not viable: no known conversion from 'char const [6]' to 'unsigned int' for argument 2}}
+    void foo(int n, char N); // expected-note {{candidate function not viable: no known conversion from 'char const [6]' to 'char' for argument 2}} 
+    void foo(int n); // expected-note {{candidate function not viable: requires 1 argument, but 2 were provided}}
+    void foo(unsigned n = 10); // expected-note {{candidate function not viable: requires at most 1 argument, but 2 were provided}}
+    void foo(int n, const char *s, int t); // expected-note {{candidate function not viable: requires 3 arguments, but 2 were provided}}
+    void foo(int n, const char *s, int t, ...); // expected-note {{candidate function not viable: requires at least 3 arguments, but 2 were provided}}
+    void foo(int n, const char *s, int t, int u = 0); // expected-note {{candidate function not viable: requires at least 3 arguments, but 2 were provided}}
+  };
+
+  void test() {
+    A a;
+    a.foo(4, "hello"); //expected-error {{no matching member function for call to 'foo'}}
+  }
+}
+