Skip to content

[math] Use std::function in ROOT::Math::ParamFunctor - #23211

Open
guitargeek wants to merge 1 commit into
root-project:masterfrom
guitargeek:paramfunctor-std-function
Open

[math] Use std::function in ROOT::Math::ParamFunctor#23211
guitargeek wants to merge 1 commit into
root-project:masterfrom
guitargeek:paramfunctor-std-function

Conversation

@guitargeek

Copy link
Copy Markdown
Contributor

ParamFunctor was still carrying the hand-rolled type erasure that the other ROOT::Math functors got rid of in 6c68bbd and a24465f: a ParamFunctionBase interface, ParamFunctorHandler and ParamMemFunHandler implementations of it, three FuncEvaluator partial specialisations to tell pointer types apart, a manual Clone(), a raw owning Impl * with hand-written copy constructor, assignment operator and destructor, and about 40 lines of commented-out code.

All of that is what std::function does, and the class already had a std::function constructor sitting next to it. Store a single std::function<T(const T *, const double *)> instead and let the compiler generate the copy operations.

The three callable shapes the FuncEvaluator specialisations used to dispatch on are kept by normalising them in one Adapt() helper: a callable taking const pointers is stored as is, a callable insisting on non-const pointers (the classic T (T *x, double *p) signature) gets them cast for it, and a pointer to a callable object is called through without taking ownership of it.

Constructing and calling a ParamFunctor is unchanged. The removed GetImpl() and SetFunction() were only handles on the deleted ParamFunctionBase and had no callers.

The <iostream> include went away with the code that needed it; two files that were picking it up transitively via TF1.h now include it themselves.

🤖 Done with the help of AI

Comment thread hist/hist/src/TEfficiency.cxx Outdated
Comment thread tutorials/analysis/unfold/testUnfold2.C Outdated
Comment thread math/mathcore/inc/Math/ParamFunctor.h Outdated
Comment thread math/mathcore/inc/Math/ParamFunctor.h Outdated
Comment thread math/mathcore/inc/Math/ParamFunctor.h Outdated
Comment thread math/mathcore/inc/Math/ParamFunctor.h Outdated
@github-actions

github-actions Bot commented Sep 1, 2026

Copy link
Copy Markdown

Test Results

    23 files      23 suites   3d 18h 37m 35s ⏱️
 3 861 tests  3 860 ✅ 0 💤 1 ❌
79 602 runs  79 601 ✅ 0 💤 1 ❌

For more details on these failures, see this check.

Results for commit efb4d48.

♻️ This comment has been updated with latest results.

@couet
couet removed their request for review September 3, 2026 08:48
`ParamFunctor` was still carrying the hand-rolled type erasure that the
other `ROOT::Math` functors got rid of in 6c68bbd and a24465f:
a `ParamFunctionBase` interface, `ParamFunctorHandler` and
`ParamMemFunHandler` implementations of it, three `FuncEvaluator` partial
specialisations to tell pointer types apart, a manual `Clone()`, a raw
owning `Impl *` with hand-written copy constructor, assignment operator
and destructor, and about 40 lines of commented-out code.

All of that is what `std::function` does, and the class already had a
`std::function` constructor sitting next to it. Store a single
`std::function<T(const T *, const double *)>` instead and let the
compiler generate the copy operations.

The three callable shapes the `FuncEvaluator` specialisations used to
dispatch on are kept by normalising them in one `Adapt()` helper: a
callable taking const pointers is stored as is, a callable insisting on
non-const pointers (the classic `T (T *x, double *p)` signature) gets
them cast for it, and a pointer to a callable object is called through
without taking ownership of it.

That makes the separate `FreeFunc` constructor redundant, since `Adapt()`
already normalises a free function pointer, so it goes. Nothing in ROOT
converted a free function to a `ParamFunctor` implicitly. The
`std::function` constructor stays implicit, on the other hand, because
PyROOT needs it: cppyy binds a Python-side callable to the
`TF1(const char *, ROOT::Math::ParamFunctor, ...)` overload through that
conversion, and `tutorials/math/fit/fitNormSum.py` fails to find a viable
overload without it.

Calling a `ParamFunctor` is unchanged, and so is constructing one, with
one further exception: the constructor from an object and one of its
member functions now takes a plain `Obj *` rather than a `const PtrObj &`
that only had to be dereferenceable. Every caller passes a raw pointer,
and spelling that out rejects at the signature what used to fail inside
the handler. The removed `GetImpl()` and `SetFunction()` were only handles
on the deleted `ParamFunctionBase` and had no callers.

🤖 Done with the help of AI
@guitargeek
guitargeek force-pushed the paramfunctor-std-function branch from 3b30a35 to efb4d48 Compare September 3, 2026 10:34

@hageboeck hageboeck left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the changes! It looks like now we can go even further.

Comment on lines +59 to +60
template <typename Func>
explicit ParamFunctorTempl(const Func &f) : fFunc{Adapt(f)}

@hageboeck hageboeck Sep 4, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about making this a universal reference using

template<typename Func>
explicit ParamFunctorTempl(Func &&f)

This would allow for moving an object into the functor. Then, since after the refactoring (thanks!) Adapt is only used in a single place, we could move its entire body right here.
And finally, see my comment below.

return [f](const T *x, const double *p) { return (*f)(const_cast<T *>(x), const_cast<double *>(p)); };
}
} else if constexpr (std::is_invocable_v<Func &, const T *, const double *>) {
return std::move(f);

@hageboeck hageboeck Sep 4, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if we could simplify this by starting the block as follows:

if constexpr (std::is_constructible_v<std::function<Signature>, Func>) {
   return {std::forward<Func>(f)};
   // Or if moved into the body of the constructor:
   fFunc = std::function<Signature>{std::forward<Func>(f)};
} else if (...) {

Like this, we delegate everything we can immediately to the std::function constructor. The other cases will probably only have to deal with the constness if I understand correctly.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants