From 4bbe85c5d4e7535a55e3455de310868067d0182a Mon Sep 17 00:00:00 2001 From: Richard Smith Date: Fri, 6 Sep 2019 23:53:21 +0000 Subject: [PATCH] Implement demangling support for C++20 lambda expression extensions. This implements demangling support for the mangling extensions specified in https://github.com/itanium-cxx-abi/cxx-abi/pull/85, much of which is implemented in Clang r359967 and r371004. Specifically, this provides demangling for: * in * with non-zero level * lambda-expression literals (not emitted by Clang yet) * nullptr literals * string literals (The final two seem unrelated, but handling them was necessary in order to disambiguate between lambda expressions and the other forms of literal for which we have a type but no value.) When demangling a , we form template parameters with no corresponding argument, so we cannot substitute in the argument in the demangling. Instead we invent synthetic names for the template parameters (eg, '[]($T *x)'). llvm-svn: 371273 --- libcxxabi/src/cxa_demangle.cpp | 10 + libcxxabi/src/demangle/ItaniumDemangle.h | 436 +++++++++++++++++++++-- libcxxabi/test/test_demangle.pass.cpp | 14 +- 3 files changed, 429 insertions(+), 31 deletions(-) diff --git a/libcxxabi/src/cxa_demangle.cpp b/libcxxabi/src/cxa_demangle.cpp index 7912b1ce00b369..7c18268c4c0ba9 100644 --- a/libcxxabi/src/cxa_demangle.cpp +++ b/libcxxabi/src/cxa_demangle.cpp @@ -171,6 +171,16 @@ struct DumpVisitor { return printStr("SpecialSubKind::iostream"); } } + void print(TemplateParamKind TPK) { + switch (TPK) { + case TemplateParamKind::Type: + return printStr("TemplateParamKind::Type"); + case TemplateParamKind::NonType: + return printStr("TemplateParamKind::NonType"); + case TemplateParamKind::Template: + return printStr("TemplateParamKind::Template"); + } + } void newLine() { printStr("\n"); diff --git a/libcxxabi/src/demangle/ItaniumDemangle.h b/libcxxabi/src/demangle/ItaniumDemangle.h index aaccb27e17a3ea..f5bb0d678c268b 100644 --- a/libcxxabi/src/demangle/ItaniumDemangle.h +++ b/libcxxabi/src/demangle/ItaniumDemangle.h @@ -57,6 +57,11 @@ X(LocalName) \ X(VectorType) \ X(PixelVectorType) \ + X(SyntheticTemplateParamName) \ + X(TypeTemplateParamDecl) \ + X(NonTypeTemplateParamDecl) \ + X(TemplateTemplateParamDecl) \ + X(TemplateParamPackDecl) \ X(ParameterPack) \ X(TemplateArgumentPack) \ X(ParameterPackExpansion) \ @@ -91,6 +96,8 @@ X(ThrowExpr) \ X(UUIDOfExpr) \ X(BoolExpr) \ + X(StringLiteral) \ + X(LambdaExpr) \ X(IntegerCastExpr) \ X(IntegerLiteral) \ X(FloatLiteral) \ @@ -303,7 +310,7 @@ inline Qualifiers operator|=(Qualifiers &Q1, Qualifiers Q2) { return Q1 = static_cast(Q1 | Q2); } -class QualType : public Node { +class QualType final : public Node { protected: const Qualifiers Quals; const Node *Child; @@ -964,6 +971,127 @@ class PixelVectorType final : public Node { } }; +enum class TemplateParamKind { Type, NonType, Template }; + +/// An invented name for a template parameter for which we don't have a +/// corresponding template argument. +/// +/// This node is created when parsing the for a lambda with +/// explicit template arguments, which might be referenced in the parameter +/// types appearing later in the . +class SyntheticTemplateParamName final : public Node { + TemplateParamKind Kind; + unsigned Index; + +public: + SyntheticTemplateParamName(TemplateParamKind Kind_, unsigned Index_) + : Node(KSyntheticTemplateParamName), Kind(Kind_), Index(Index_) {} + + template void match(Fn F) const { F(Kind, Index); } + + void printLeft(OutputStream &S) const override { + switch (Kind) { + case TemplateParamKind::Type: + S += "$T"; + break; + case TemplateParamKind::NonType: + S += "$N"; + break; + case TemplateParamKind::Template: + S += "$TT"; + break; + } + if (Index > 0) + S << Index - 1; + } +}; + +/// A template type parameter declaration, 'typename T'. +class TypeTemplateParamDecl final : public Node { + Node *Name; + +public: + TypeTemplateParamDecl(Node *Name_) + : Node(KTypeTemplateParamDecl, Cache::Yes), Name(Name_) {} + + template void match(Fn F) const { F(Name); } + + void printLeft(OutputStream &S) const override { + S += "typename "; + } + + void printRight(OutputStream &S) const override { + Name->print(S); + } +}; + +/// A non-type template parameter declaration, 'int N'. +class NonTypeTemplateParamDecl final : public Node { + Node *Name; + Node *Type; + +public: + NonTypeTemplateParamDecl(Node *Name_, Node *Type_) + : Node(KNonTypeTemplateParamDecl, Cache::Yes), Name(Name_), Type(Type_) {} + + template void match(Fn F) const { F(Name, Type); } + + void printLeft(OutputStream &S) const override { + Type->printLeft(S); + if (!Type->hasRHSComponent(S)) + S += " "; + } + + void printRight(OutputStream &S) const override { + Name->print(S); + Type->printRight(S); + } +}; + +/// A template template parameter declaration, +/// 'template typename N'. +class TemplateTemplateParamDecl final : public Node { + Node *Name; + NodeArray Params; + +public: + TemplateTemplateParamDecl(Node *Name_, NodeArray Params_) + : Node(KTemplateTemplateParamDecl, Cache::Yes), Name(Name_), + Params(Params_) {} + + template void match(Fn F) const { F(Name, Params); } + + void printLeft(OutputStream &S) const override { + S += "template<"; + Params.printWithComma(S); + S += "> typename "; + } + + void printRight(OutputStream &S) const override { + Name->print(S); + } +}; + +/// A template parameter pack declaration, 'typename ...T'. +class TemplateParamPackDecl final : public Node { + Node *Param; + +public: + TemplateParamPackDecl(Node *Param_) + : Node(KTemplateParamPackDecl, Cache::Yes), Param(Param_) {} + + template void match(Fn F) const { F(Param); } + + void printLeft(OutputStream &S) const override { + Param->printLeft(S); + S += "..."; + } + + void printRight(OutputStream &S) const override { + Param->printRight(S); + } +}; + /// An unexpanded parameter pack (either in the expression or type context). If /// this AST is correct, this node will have a ParameterPackExpansion node above /// it. @@ -1410,21 +1538,36 @@ class UnnamedTypeName : public Node { }; class ClosureTypeName : public Node { + NodeArray TemplateParams; NodeArray Params; StringView Count; public: - ClosureTypeName(NodeArray Params_, StringView Count_) - : Node(KClosureTypeName), Params(Params_), Count(Count_) {} + ClosureTypeName(NodeArray TemplateParams_, NodeArray Params_, + StringView Count_) + : Node(KClosureTypeName), TemplateParams(TemplateParams_), + Params(Params_), Count(Count_) {} - template void match(Fn F) const { F(Params, Count); } + template void match(Fn F) const { + F(TemplateParams, Params, Count); + } + + void printDeclarator(OutputStream &S) const { + if (!TemplateParams.empty()) { + S += "<"; + TemplateParams.printWithComma(S); + S += ">"; + } + S += "("; + Params.printWithComma(S); + S += ")"; + } void printLeft(OutputStream &S) const override { S += "\'lambda"; S += Count; - S += "\'("; - Params.printWithComma(S); - S += ")"; + S += "\'"; + printDeclarator(S); } }; @@ -1902,6 +2045,38 @@ class BoolExpr : public Node { } }; +class StringLiteral : public Node { + const Node *Type; + +public: + StringLiteral(const Node *Type_) : Node(KStringLiteral), Type(Type_) {} + + template void match(Fn F) const { F(Type); } + + void printLeft(OutputStream &S) const override { + S += "\"<"; + Type->print(S); + S += ">\""; + } +}; + +class LambdaExpr : public Node { + const Node *Type; + + void printLambdaDeclarator(OutputStream &S) const; + +public: + LambdaExpr(const Node *Type_) : Node(KLambdaExpr), Type(Type_) {} + + template void match(Fn F) const { F(Type); } + + void printLeft(OutputStream &S) const override { + S += "[]"; + printLambdaDeclarator(S); + S += "{...}"; + } +}; + class IntegerCastExpr : public Node { // ty(integer) const Node *Ty; @@ -2034,6 +2209,39 @@ FOR_EACH_NODE_KIND(SPECIALIZATION) #undef FOR_EACH_NODE_KIND +inline void LambdaExpr::printLambdaDeclarator(OutputStream &S) const { + struct LambdaDeclaratorPrinter { + OutputStream &S; + void operator()(const ClosureTypeName *LambdaType) { + LambdaType->printDeclarator(S); + } + + // Walk through any qualifiers to find the lambda-expression. + void operator()(const SpecialName *Name) { + Name->match([&](StringView, const Node *Name) { Name->visit(*this); }); + } + void operator()(const NestedName *Name) { + Name->match([&](const Node *, const Node *Name) { Name->visit(*this); }); + } + void operator()(const LocalName *Name) { + Name->match([&](const Node *, const Node *Name) { Name->visit(*this); }); + } + void operator()(const QualifiedName *Name) { + Name->match([&](const Node *, const Node *Name) { Name->visit(*this); }); + } + void operator()(const GlobalQualifiedName *Name) { + Name->match([&](const Node *Child) { Child->visit(*this); }); + } + void operator()(const StdQualifiedName *Name) { + Name->match([&](const Node *Child) { Child->visit(*this); }); + } + void operator()(const Node *) { + // If we can't find the lambda type, just print '[]{...}'. + } + }; + return Type->visit(LambdaDeclaratorPrinter{S}); +} + template class PODSmallVector { static_assert(std::is_pod::value, @@ -2167,10 +2375,39 @@ template struct AbstractManglingParser { // table. PODSmallVector Subs; + using TemplateParamList = PODSmallVector; + + class ScopedTemplateParamList { + AbstractManglingParser *Parser; + size_t OldNumTemplateParamLists; + TemplateParamList Params; + + public: + ScopedTemplateParamList(AbstractManglingParser *Parser) + : Parser(Parser), + OldNumTemplateParamLists(Parser->TemplateParams.size()) { + Parser->TemplateParams.push_back(&Params); + } + ~ScopedTemplateParamList() { + assert(Parser->TemplateParams.size() >= OldNumTemplateParamLists); + Parser->TemplateParams.dropBack(OldNumTemplateParamLists); + } + void push_back(Node *Param) { + Params.push_back(Param); + } + }; + // Template parameter table. Like the above, but referenced like "T42_". // This has a smaller size compared to Subs and Names because it can be // stored on the stack. - PODSmallVector TemplateParams; + TemplateParamList OuterTemplateParams; + + // Lists of template parameters indexed by template parameter depth, + // referenced like "TL2_4_". If nonempty, element 0 is always + // OuterTemplateParams; inner elements are always template parameter lists of + // lambda expressions. For a generic lambda with no explicit template + // parameter list, the corresponding parameter list pointer will be null. + PODSmallVector TemplateParams; // Set of unresolved forward references. These can occur in a // conversion operator's type, and are resolved in the enclosing . @@ -2178,7 +2415,9 @@ template struct AbstractManglingParser { bool TryToParseTemplateArgs = true; bool PermitForwardTemplateReferences = false; - bool ParsingLambdaParams = false; + size_t ParsingLambdaParamsAtLevel = (size_t)-1; + + unsigned NumSyntheticTemplateParameters[3] = {}; Alloc ASTAllocator; @@ -2193,9 +2432,10 @@ template struct AbstractManglingParser { Names.clear(); Subs.clear(); TemplateParams.clear(); - ParsingLambdaParams = false; + ParsingLambdaParamsAtLevel = (size_t)-1; TryToParseTemplateArgs = true; PermitForwardTemplateReferences = false; + NumSyntheticTemplateParameters = {}; ASTAllocator.reset(); } @@ -2253,6 +2493,7 @@ template struct AbstractManglingParser { bool parseSeqId(size_t *Out); Node *parseSubstitution(); Node *parseTemplateParam(); + Node *parseTemplateParamDecl(); Node *parseTemplateArgs(bool TagTemplates = false); Node *parseTemplateArg(); @@ -2301,9 +2542,10 @@ template struct AbstractManglingParser { size_t E = ForwardTemplateRefs.size(); for (; I < E; ++I) { size_t Idx = ForwardTemplateRefs[I]->Index; - if (Idx >= TemplateParams.size()) + if (TemplateParams.empty() || !TemplateParams[0] || + Idx >= TemplateParams[0]->size()) return true; - ForwardTemplateRefs[I]->Ref = TemplateParams[Idx]; + ForwardTemplateRefs[I]->Ref = (*TemplateParams[0])[Idx]; } ForwardTemplateRefs.dropBack(State.ForwardTemplateRefsBegin); return false; @@ -2470,7 +2712,12 @@ AbstractManglingParser::parseUnqualifiedName(NameState *State) { // ::= + # Parameter types or "v" if the lambda has no parameters template Node * -AbstractManglingParser::parseUnnamedTypeName(NameState *) { +AbstractManglingParser::parseUnnamedTypeName(NameState *State) { + // refer to the innermost . Clear out any + // outer args that we may have inserted into TemplateParams. + if (State != nullptr) + TemplateParams.clear(); + if (consumeIf("Ut")) { StringView Count = parseNumber(); if (!consumeIf('_')) @@ -2478,22 +2725,60 @@ AbstractManglingParser::parseUnnamedTypeName(NameState *) { return make(Count); } if (consumeIf("Ul")) { - NodeArray Params; - SwapAndRestore SwapParams(ParsingLambdaParams, true); + SwapAndRestore SwapParams(ParsingLambdaParamsAtLevel, + TemplateParams.size()); + ScopedTemplateParamList LambdaTemplateParams(this); + + size_t ParamsBegin = Names.size(); + while (look() == 'T' && + StringView("yptn").find(look(1)) != StringView::npos) { + Node *T = parseTemplateParamDecl(); + if (!T) + return nullptr; + LambdaTemplateParams.push_back(T); + Names.push_back(T); + } + NodeArray TempParams = popTrailingNodeArray(ParamsBegin); + + // FIXME: If TempParams is empty and none of the function parameters + // includes 'auto', we should remove LambdaTemplateParams from the + // TemplateParams list. Unfortunately, we don't find out whether there are + // any 'auto' parameters until too late in an example such as: + // + // template void f( + // decltype([](decltype([](T v) {}), + // auto) {})) {} + // template void f( + // decltype([](decltype([](T w) {}), + // int) {})) {} + // + // Here, the type of v is at level 2 but the type of w is at level 1. We + // don't find this out until we encounter the type of the next parameter. + // + // However, compilers can't actually cope with the former example in + // practice, and it's likely to be made ill-formed in future, so we don't + // need to support it here. + // + // If we encounter an 'auto' in the function parameter types, we will + // recreate a template parameter scope for it, but any intervening lambdas + // will be parsed in the 'wrong' template parameter depth. + if (TempParams.empty()) + TemplateParams.pop_back(); + if (!consumeIf("vE")) { - size_t ParamsBegin = Names.size(); do { Node *P = getDerived().parseType(); if (P == nullptr) return nullptr; Names.push_back(P); } while (!consumeIf('E')); - Params = popTrailingNodeArray(ParamsBegin); } + NodeArray Params = popTrailingNodeArray(ParamsBegin); + StringView Count = parseNumber(); if (!consumeIf('_')) return nullptr; - return make(Params, Count); + return make(TempParams, Params, Count); } if (consumeIf("Ub")) { (void)parseNumber(); @@ -3949,6 +4234,7 @@ Node *AbstractManglingParser::parseConversionExpr() { // ::= L E # floating literal // ::= L E # string literal // ::= L E # nullptr literal (i.e., "LDnE") +// ::= L E # lambda expression // FIXME: ::= L _ E # complex floating point literal (C 2000) // ::= L E # external name template @@ -4020,6 +4306,19 @@ Node *AbstractManglingParser::parseExprPrimary() { return R; } return nullptr; + case 'A': { + Node *T = getDerived().parseType(); + if (T == nullptr) + return nullptr; + // FIXME: We need to include the string contents in the mangling. + if (consumeIf('E')) + return make(T); + return nullptr; + } + case 'D': + if (consumeIf("DnE")) + return make("nullptr"); + return nullptr; case 'T': // Invalid mangled name per // http://sourcerytools.com/pipermail/cxx-abi-dev/2011-August/002422.html @@ -4036,7 +4335,7 @@ Node *AbstractManglingParser::parseExprPrimary() { return make(T, N); } if (consumeIf('E')) - return T; + return make(T); return nullptr; } } @@ -5062,11 +5361,22 @@ Node *AbstractManglingParser::parseSubstitution() { // ::= T_ # first template parameter // ::= T _ +// ::= TL __ +// ::= TL _ _ template Node *AbstractManglingParser::parseTemplateParam() { if (!consumeIf('T')) return nullptr; + size_t Level = 0; + if (consumeIf('L')) { + if (parsePositiveInteger(&Level)) + return nullptr; + ++Level; + if (!consumeIf('_')) + return nullptr; + } + size_t Index = 0; if (!consumeIf('_')) { if (parsePositiveInteger(&Index)) @@ -5076,15 +5386,11 @@ Node *AbstractManglingParser::parseTemplateParam() { return nullptr; } - // Itanium ABI 5.1.8: In a generic lambda, uses of auto in the parameter list - // are mangled as the corresponding artificial template type parameter. - if (ParsingLambdaParams) - return make("auto"); - // If we're in a context where this refers to a // further ahead in the mangled name (currently just conversion // operator types), then we should only look it up in the right context. - if (PermitForwardTemplateReferences) { + // This can only happen at the outermost level. + if (PermitForwardTemplateReferences && Level == 0) { Node *ForwardRef = make(Index); if (!ForwardRef) return nullptr; @@ -5094,9 +5400,78 @@ Node *AbstractManglingParser::parseTemplateParam() { return ForwardRef; } - if (Index >= TemplateParams.size()) + if (Level >= TemplateParams.size() || !TemplateParams[Level] || + Index >= TemplateParams[Level]->size()) { + // Itanium ABI 5.1.8: In a generic lambda, uses of auto in the parameter + // list are mangled as the corresponding artificial template type parameter. + if (ParsingLambdaParamsAtLevel == Level && Level <= TemplateParams.size()) { + // This will be popped by the ScopedTemplateParamList in + // parseUnnamedTypeName. + if (Level == TemplateParams.size()) + TemplateParams.push_back(nullptr); + return make("auto"); + } + return nullptr; - return TemplateParams[Index]; + } + + return (*TemplateParams[Level])[Index]; +} + +// ::= Ty # type parameter +// ::= Tn # non-type parameter +// ::= Tt * E # template parameter +// ::= Tp # parameter pack +template +Node *AbstractManglingParser::parseTemplateParamDecl() { + auto InventTemplateParamName = [&](TemplateParamKind Kind) { + unsigned Index = NumSyntheticTemplateParameters[(int)Kind]++; + Node *N = make(Kind, Index); + if (N) TemplateParams.back()->push_back(N); + return N; + }; + + if (consumeIf("Ty")) { + Node *Name = InventTemplateParamName(TemplateParamKind::Type); + if (!Name) + return nullptr; + return make(Name); + } + + if (consumeIf("Tn")) { + Node *Name = InventTemplateParamName(TemplateParamKind::NonType); + if (!Name) + return nullptr; + Node *Type = parseType(); + if (!Type) + return nullptr; + return make(Name, Type); + } + + if (consumeIf("Tt")) { + Node *Name = InventTemplateParamName(TemplateParamKind::Template); + if (!Name) + return nullptr; + size_t ParamsBegin = Names.size(); + ScopedTemplateParamList TemplateTemplateParamParams(this); + while (!consumeIf("E")) { + Node *P = parseTemplateParamDecl(); + if (!P) + return nullptr; + Names.push_back(P); + } + NodeArray Params = popTrailingNodeArray(ParamsBegin); + return make(Name, Params); + } + + if (consumeIf("Tp")) { + Node *P = parseTemplateParamDecl(); + if (!P) + return nullptr; + return make(P); + } + + return nullptr; } // ::= # type or template @@ -5153,8 +5528,11 @@ AbstractManglingParser::parseTemplateArgs(bool TagTemplates) { // refer to the innermost . Clear out any // outer args that we may have inserted into TemplateParams. - if (TagTemplates) + if (TagTemplates) { TemplateParams.clear(); + TemplateParams.push_back(&OuterTemplateParams); + OuterTemplateParams.clear(); + } size_t ArgsBegin = Names.size(); while (!consumeIf('E')) { @@ -5172,7 +5550,7 @@ AbstractManglingParser::parseTemplateArgs(bool TagTemplates) { if (!TableEntry) return nullptr; } - TemplateParams.push_back(TableEntry); + TemplateParams.back()->push_back(TableEntry); } else { Node *Arg = getDerived().parseTemplateArg(); if (Arg == nullptr) diff --git a/libcxxabi/test/test_demangle.pass.cpp b/libcxxabi/test/test_demangle.pass.cpp index 220c2fee9e27c0..9f7f1671a5abcf 100644 --- a/libcxxabi/test/test_demangle.pass.cpp +++ b/libcxxabi/test/test_demangle.pass.cpp @@ -29582,8 +29582,8 @@ const char* cases[][2] = {"_ZSteqIcEN9__gnu_cxx11__enable_ifIXsr9__is_charIT_EE7__valueEbE6__typeERKSbIS2_St11char_traitsIS2_ESaIS2_EESA_", "__gnu_cxx::__enable_if<__is_char::__value, bool>::__type std::operator==(std::basic_string, std::allocator > const&, std::basic_string, std::allocator > const&)"}, {"_ZZ10+[Foo bar]E3Baz", "+[Foo bar]::Baz"}, {"_ZN9__gnu_cxx17__normal_iteratorIPKSt4pairISsbESt6vectorIS2_SaIS2_EEEC5ERKS4_", "__gnu_cxx::__normal_iterator const*, std::vector, std::allocator > > >::__normal_iterator(std::pair const* const&)"}, - {"_Z1fIiEDTeqfp_LDnEEPT_", "decltype((fp) == (std::nullptr_t)) f(int*)"}, - {"_Z1fIiEDTeqfp1_LDnEEicPT_", "decltype((fp1) == (std::nullptr_t)) f(int, char, int*)"}, + {"_Z1fIiEDTeqfp_LDnEEPT_", "decltype((fp) == (nullptr)) f(int*)"}, + {"_Z1fIiEDTeqfp1_LDnEEicPT_", "decltype((fp1) == (nullptr)) f(int, char, int*)"}, {"_ZZN1S1fEiiEd0_NKUlvE_clEv", "S::f(int, int)::'lambda'()::operator()() const"}, {"_Z3fooPM2ABi", "foo(int AB::**)"}, {"_Z1rM1GFivEMS_KFivES_M1HFivES1_4whatIKS_E5what2IS8_ES3_", "r(int (G::*)(), int (G::*)() const, G, int (H::*)(), int (G::*)(), what, what2, int (G::*)() const)"}, @@ -29669,6 +29669,10 @@ const char* cases[][2] = {"_ZN6test481fINS_1SEEEvPTuNT_1uE", "void test48::f(union test48::S::u*)"}, {"_ZN6test451fINS_1SEEEvPTeNT_1eE", "void test45::f(enum test45::S::e*)"}, + // String literals + // FIXME: We need to encode the string contents in order to avoid symbol collisions. + {"_Z1fIcEvDTcv3StrIT_ELA6_KcEE", "void f(decltype((Str)(\"\")))"}, + // Initializer list expressions {"_ZN5test43tf2INS_1XEEEvDTnw_T_piilLi1EEEE", "void test4::tf2(decltype(new test4::X({1})))"}, {"_ZN5test73fA1IiEEDTcmtlNS_1AELi1ELi2EEcvT__EES2_", "decltype((test7::A{1, 2}) , ((int)())) test7::fA1(int)"}, @@ -29775,6 +29779,12 @@ const char* cases[][2] = // C++2a char8_t: {"_ZTSPDu", "typeinfo name for char8_t*"}, + + // C++2a lambda-expressions: + {"_ZNK1xMUlTyT_E_clIiEEDaS_", "auto x::'lambda'($T)::operator()(x) const"}, + {"_ZNK1xMUlTnPA3_ivE_clILS0_0EEEDav", "auto x::'lambda'()::operator()<(int [3])0>() const"}, + {"_ZNK1xMUlTyTtTyTnT_TpTnPA3_TL0__ETpTyvE_clIi1XJfEEEDav", "auto x::'lambda' typename $TT, typename ...$T1>()::operator()() const"}, + {"_ZN1AIiE1fIfEEvDTLZ1AIiEEUlTyTtTyTnTL1__ETL0_1_T_TL0__E_EE", "void A::f(decltype([] typename $TT>($TT, int, $T){...}))"}, }; const unsigned N = sizeof(cases) / sizeof(cases[0]);