Summary
When a function object variable is a concrete instantiation of a class template, the lifted operator() signature still contains unresolved template parameters. For example:
template<typename T>
struct compare_fn {
bool operator()(T const& a, T const& b) const;
};
constexpr compare_fn<int> compare_int = {};
The generated documentation for compare_int shows operator()(T const& a, T const& b) instead of operator()(int const& a, int const& b).
This does not affect variable templates (where the parameter is intentionally unresolved) or non-template structs with template operator(), both of which already work correctly.
Proposed approach
The template arguments are already available in the metadata. The variable's type carries a SpecializationName with TemplateArgs, and the record's TemplateInfo has the corresponding Params. The substitution could happen in FunctionObjectFinalizer::processVariable(), after the synthetic function is created from the original operator():
- Build a mapping from template parameter names to concrete types by pairing
record.Template.Params[i] with specName.TemplateArgs[i].
- Recursively walk the synthetic function's return type and parameter types.
- Replace any
NamedType that matches a template parameter name with the concrete type.
Open questions
Feedback welcome on any of these:
- Substitution depth. The recursive walk needs to handle all
TypeKind variants: pointers, references, arrays, function types, member pointers, and nested specializations (e.g., std::vector<T> → std::vector<int>). Are there type kinds where substitution should be skipped or handled specially?
- Name matching. Template parameter names like
T are stored as string identifiers in Name. Is string matching against record.Template.Params[i].Name sufficient, or are there cases where the same name could appear at different template depths and cause ambiguity?
- Non-type and template-template parameters. The initial implementation could focus on type parameters only. Are there realistic function object patterns that use non-type template parameters (e.g.,
template<int N>) or template-template parameters in operator() signatures?
- Finalization vs. extraction. Should the substitution happen at finalization time (in
FunctionObjectFinalizer) or earlier during AST extraction? Finalization keeps the change localized, but extraction has access to Clang's SubstTemplateTypeParmType which might already carry the resolved types.
- Partial specializations. If
compare_fn<int> is a partial specialization with a different operator() signature, does the current finalizer already pick up the specialization's members, or does it always look at the primary template?
Context
This came up during the function object support work (PR #1157). The compare_int / compare_double test cases in test-files/golden-tests/symbols/variable/function-objects.cpp show the current behavior.
Summary
When a function object variable is a concrete instantiation of a class template, the lifted
operator()signature still contains unresolved template parameters. For example:The generated documentation for
compare_intshowsoperator()(T const& a, T const& b)instead ofoperator()(int const& a, int const& b).This does not affect variable templates (where the parameter is intentionally unresolved) or non-template structs with template
operator(), both of which already work correctly.Proposed approach
The template arguments are already available in the metadata. The variable's type carries a
SpecializationNamewithTemplateArgs, and the record'sTemplateInfohas the correspondingParams. The substitution could happen inFunctionObjectFinalizer::processVariable(), after the synthetic function is created from the originaloperator():record.Template.Params[i]withspecName.TemplateArgs[i].NamedTypethat matches a template parameter name with the concrete type.Open questions
Feedback welcome on any of these:
TypeKindvariants: pointers, references, arrays, function types, member pointers, and nested specializations (e.g.,std::vector<T>→std::vector<int>). Are there type kinds where substitution should be skipped or handled specially?Tare stored as string identifiers inName. Is string matching againstrecord.Template.Params[i].Namesufficient, or are there cases where the same name could appear at different template depths and cause ambiguity?template<int N>) or template-template parameters inoperator()signatures?FunctionObjectFinalizer) or earlier during AST extraction? Finalization keeps the change localized, but extraction has access to Clang'sSubstTemplateTypeParmTypewhich might already carry the resolved types.compare_fn<int>is a partial specialization with a differentoperator()signature, does the current finalizer already pick up the specialization's members, or does it always look at the primary template?Context
This came up during the function object support work (PR #1157). The
compare_int/compare_doubletest cases in test-files/golden-tests/symbols/variable/function-objects.cpp show the current behavior.