Skip to content

Explore: resolve template parameters when lifting operator() from class templates #1165

@gennaroprota

Description

@gennaroprota

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions