From 0480d6ba0bdac6eb6dd1adc8353b79c8a1153826 Mon Sep 17 00:00:00 2001 From: Jonas Rembser Date: Mon, 31 Aug 2026 16:30:13 +0000 Subject: [PATCH] [math] Use `std::function` in `ROOT::Math::ParamFunctor` MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `ParamFunctor` was still carrying the hand-rolled type erasure that the other `ROOT::Math` functors got rid of in 6c68bbdc42b and a24465f7ce3: 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` 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 --- README/ReleaseNotes/v642/index.md | 1 + math/mathcore/inc/Math/ParamFunctor.h | 386 +++----------------------- 2 files changed, 47 insertions(+), 340 deletions(-) diff --git a/README/ReleaseNotes/v642/index.md b/README/ReleaseNotes/v642/index.md index 293187bde9380..7a16b865e42e2 100644 --- a/README/ReleaseNotes/v642/index.md +++ b/README/ReleaseNotes/v642/index.md @@ -69,6 +69,7 @@ Users are encouraged to export their models to ONNX and use the retained ONNX pa * Support for the AIX operating system has been removed from the codebase. This support has not been tested since the late v5 releases and the LLVM JIT is not yet supporting AIX. * The headers Htypes.h and Gtypes.h that were deprecated in ROOT 6.20 will now emit warnings and will be fully removed in ROOT 6.44. * The header GLConstants.h is no longer part of ROOT installed headers. +* The `ROOT::Math::ParamFunctionBase`, `ROOT::Math::ParamFunctorHandler` and `ROOT::Math::ParamMemFunHandler` classes in `Math/ParamFunctor.h` are removed, together with the `ParamFunctor::GetImpl()` and `ParamFunctor::SetFunction()` methods that exposed them. They implemented the type erasure that `ParamFunctor` now gets from `std::function`, mirroring what was already done for `ROOT::Math::Functor`. Constructing and calling a `ParamFunctor` is unchanged, except that the constructor from an object and one of its member functions now takes a plain pointer to the object instead of anything dereferenceable, so smart pointers are no longer accepted there. ## Build System diff --git a/math/mathcore/inc/Math/ParamFunctor.h b/math/mathcore/inc/Math/ParamFunctor.h index 789192f0b5600..aa6ae02553747 100644 --- a/math/mathcore/inc/Math/ParamFunctor.h +++ b/math/mathcore/inc/Math/ParamFunctor.h @@ -9,256 +9,20 @@ **********************************************************************/ // Header file for Functor classes. -// design is inspired by the Loki Functor #ifndef ROOT_Math_ParamFunctor #define ROOT_Math_ParamFunctor -// #ifndef ROOT_Math_IFunction -// #include "Math/IFunction.h" -// #endif - -// #ifndef Root_Math_StaticCheck -// #include "Math/StaticCheck.h" -// #endif - -//#include - #include "RtypesCore.h" + #include -#include +#include +#include namespace ROOT { namespace Math { -/** - * \defgroup ParamFunctor_int N-D parametric functions - * \brief Multi-dimensional parametric functions - * \ingroup Math - */ - -/** class defining the signature for multi-dim parametric functions - - @ingroup ParamFunctor_int - */ -template -class ParamFunctionBase { - public: - virtual ~ParamFunctionBase() {} - virtual T operator() (const T * x, const double *p) = 0; - virtual T operator() (T * x, double *p) = 0; - virtual ParamFunctionBase * Clone() const = 0; -}; - - - -/** - ParamFunctor Handler class is responsible for wrapping any other functor and pointer to - free C functions. - It can be created from any function implementing the correct signature - corresponding to the requested type - - @ingroup ParamFunctor_int - -*/ - -template -class ParamFunctorHandler : public ParentFunctor::Impl { - - typedef typename ParentFunctor::EvalType EvalType; - typedef typename ParentFunctor::Impl Base; - -public: - - // constructor - ParamFunctorHandler(const Func & fun) : fFunc(fun) {} - - - virtual ~ParamFunctorHandler() {} - - - // for 1D functions - inline EvalType operator() (EvalType x, double *p) { - return fFunc(x,p); - } -// inline double operator() (double x, const double *p) const { -// return fFunc(x,p); -// } - // for multi-dimensional functions -// inline double operator() (const double * x, const double *p) const { -// return fFunc(x,p); -// } - inline EvalType operator() (EvalType * x, double *p) override { - return FuncEvaluator::Eval(fFunc,x,p); - } - - inline EvalType operator() (const EvalType * x, const double *p) override { - return FuncEvaluator::EvalConst(fFunc,x,p); - } - - // clone (use same pointer) - ParamFunctorHandler * Clone() const override { - return new ParamFunctorHandler(fFunc); - } - - -private : - - Func fFunc; - - // structure to distinguish pointer types - template struct FuncEvaluator { - inline static T Eval( F & f, T *x, double * p) { - return f(x, p); - } - - inline static T EvalConst( F & f, const T *x, const double * p) { - return f((T*)x, (double*)p); - } - }; - - template struct FuncEvaluator { - inline static T Eval( F * f, T *x, double * p) { - return (*f)(x, p); - } - - inline static T EvalConst( F * f, const T *x, const double * p) { - return (*f)((T*)x, (double*)p); - - } - }; - - template struct FuncEvaluator { - inline static T Eval( const F * f, T *x, double * p) { - return (*f)(x, p); - } - - inline static T EvalConst( const F * f, const T *x, const double * p) { - return (*f)((T*)x, (double*)p); - } - }; - - // need maybe also volatile ? -}; - - -#if defined(__ROOTCLING__) || defined(G__DICTIONARY) -// needed since Cling initialize it with TRootIOCtor -//class TRootIOCtor; -template -class ParamFunctorHandler : public ParentFunctor::Impl -{ -public: - - ParamFunctorHandler(TRootIOCtor *) {} - - double operator() (double *, double * ) { return 0; } - - double operator() (const double *, const double * ) { return 0; } - // clone (use same pointer) - ParamFunctorHandler * Clone() const { - return 0; - } - -}; -#endif - - -/** - ParamFunctor Handler to Wrap pointers to member functions - - @ingroup ParamFunctor_int -*/ -template -class ParamMemFunHandler : public ParentFunctor::Impl -{ - typedef typename ParentFunctor::Impl Base; - - -public: - - /// constructor from a pointer to the class and a pointer to the function - ParamMemFunHandler(const PointerToObj& pObj, PointerToMemFn pMemFn) - : fObj(pObj), fMemFn(pMemFn) - {} - - virtual ~ParamMemFunHandler() {} - -// inline double operator() (double x, const double * p) const { -// return ((*fObj).*fMemFn)(x,p); -// } - - inline double operator() (double x, double * p) { - return ((*fObj).*fMemFn)(x,p); - } - -// inline double operator() (const double * x, const double * p) const { -// return ((*fObj).*fMemFn)(x,p); -// } - - inline double operator() (double * x, double * p) override { - return MemFuncEvaluator::Eval(fObj,fMemFn,x,p); - } - - inline double operator() (const double * x, const double * p) override { - return MemFuncEvaluator::EvalConst(fObj,fMemFn,x,p); - } - - // clone (use same pointer) - ParamMemFunHandler * Clone() const override { - return new ParamMemFunHandler(fObj, fMemFn); - } - -private: - - // structure to distinguish pointer types - template struct MemFuncEvaluator { - inline static T Eval(PObj & pobj, F & f, T *x, double * p) { - return ((*pobj).*f)(x, p); - } - - inline static T EvalConst(PObj & pobj, F & f, const T *x, const double * p) { - return ((*pobj).*f)((T*)x, (double*)p); - } - }; - - - // // these are needed ?? - // template struct MemFuncEvaluator { - // inline static T Eval(PObj & pobj, F * f, T *x, double * p) { - // return ((*pobj).*f)f(x, p); - // } - - // inline static T EvalConst(PObj & pobj, F * f, const T *x, const double * p) { - // return ((*pobj).*f)((T*)x, (double*)p); - - // } - // }; - - // template struct FuncEvaluator { - // inline static T Eval(PObj &, const F * f, T *x, double * p) { - // return ((*pobj).*f)f(x, p); - // } - - // inline static T EvalConst(PObj & pobj, const F * f, const T *x, const double * p) { - // return ((*pobj).*f)((T*)x, (double*)p); - // } - // }; - -private : - ParamMemFunHandler(const ParamMemFunHandler&) = delete; // Not implemented - ParamMemFunHandler& operator=(const ParamMemFunHandler&) = delete; // Not implemented - - PointerToObj fObj; - PointerToMemFn fMemFn; - -}; - - - - /** Param Functor class for Multidimensional functions. It is used to wrap in a very simple and convenient way @@ -270,127 +34,69 @@ private : */ - -template -class ParamFunctorTempl { - +template +class ParamFunctorTempl { public: + using EvalType = T; - typedef T EvalType; - typedef ParamFunctionBase Impl; - - - /** - Default constructor - */ - ParamFunctorTempl () : fImpl(nullptr) {} - - - /** - construct from a pointer to member function (multi-dim type) - */ - template - ParamFunctorTempl(const PtrObj& p, MemFn memFn) - : fImpl(new ParamMemFunHandler, PtrObj, MemFn>(p, memFn)) - {} - - - - /** - construct from another generic Functor of multi-dimension - */ - template - explicit ParamFunctorTempl( const Func & f) : - fImpl(new ParamFunctorHandler,Func>(f) ) - {} + /// The signature every wrapped callable is normalized to. + using Signature = T(const T *, const double *); + ParamFunctorTempl() = default; - - // specialization used in TF1 - typedef T (* FreeFunc ) (T * , double *); - ParamFunctorTempl(FreeFunc f) : - fImpl(new ParamFunctorHandler,FreeFunc>(f) ) - { - } - - // specialization used in TF1 - ParamFunctorTempl(const std::function &func) : - fImpl(new ParamFunctorHandler, const std::function>(func)) + /// Construct from a pointer to a class object and a pointer to one of its member + /// functions, like `Foo::EvalPar(const double *x, const double *p)`. + template + ParamFunctorTempl(Obj *p, MemFn memFn) + : fFunc{[p, memFn](const T *x, const double *par) { + return (p->*memFn)(const_cast(x), const_cast(par)); + }} { } - /** - Destructor (no operations) - */ - virtual ~ParamFunctorTempl () { - if (fImpl) delete fImpl; - } - - /** - Copy constructor - */ - ParamFunctorTempl(const ParamFunctorTempl & rhs) : - fImpl(nullptr) + /// Construct from any callable object, or from a pointer to one. + /// + /// The callable is normalized to the `T (const T *, const double *)` signature: anything + /// `std::function` accepts as-is is handed to it directly, a pointer to a callable object is + /// called through without taking ownership of it, and callables that insist on non-const + /// pointers (the classic `T (T *x, double *p)` signature) get their arguments cast for them. + /// + /// The two exclusions keep this greedy forwarding reference away from overloads that a + /// non-const lvalue would otherwise bind to it by exact match: the implicit copy constructor, + /// which would end up wrapping a functor in itself, and the `std::function` conversion below, + /// which is implicit for the Python interface and would become ill-formed via this `explicit` one. + template , ParamFunctorTempl> && + !std::is_same_v, std::function>>> + explicit ParamFunctorTempl(Func &&f) { -// if (rhs.fImpl.get() != 0) -// fImpl = std::unique_ptr( (rhs.fImpl)->Clone() ); - if (rhs.fImpl) fImpl = rhs.fImpl->Clone(); - } - - /** - Assignment operator - */ - ParamFunctorTempl & operator = (const ParamFunctorTempl & rhs) { -// ParamFunctor copy(rhs); - // swap unique_ptr by hand -// Impl * p = fImpl.release(); -// fImpl.reset(copy.fImpl.release()); -// copy.fImpl.reset(p); - - if(this != &rhs) { - if (fImpl) delete fImpl; - fImpl = nullptr; - if (rhs.fImpl) - fImpl = rhs.fImpl->Clone(); + using F = std::decay_t; + if constexpr (std::is_constructible_v, Func>) { + fFunc = std::function{std::forward(f)}; + } else if constexpr (std::is_pointer_v && std::is_class_v>) { + fFunc = [f](const T *x, const double *p) { return (*f)(const_cast(x), const_cast(p)); }; + } else { + fFunc = [f = std::forward(f)](const T *x, const double *p) mutable { + return f(const_cast(x), const_cast(p)); + }; } - return *this; - } - - void * GetImpl() { return (void *) fImpl; } - - - T operator() ( T * x, double * p) { - return (*fImpl)(x,p); - } - - T operator() (const T * x, const double * p) { - return (*fImpl)(x,p); - } - - - bool Empty() const { return !fImpl; } - - - void SetFunction(Impl * f) { - fImpl = f; } -private : + /// Implicit conversion, relied on by the Python interface when passing a callable to TF1. + ParamFunctorTempl(std::function f) : fFunc{std::move(f)} {} + T operator()(const T *x, const double *p) const { return fFunc(x, p); } - //std::unique_ptr fImpl; - Impl * fImpl; - + bool Empty() const { return !fFunc; } +private: + std::function fFunc; }; - using ParamFunctor = ParamFunctorTempl; - } // end namespace Math +} // end namespace Math } // end namespace ROOT - #endif /* ROOT_Math_ParamFunctor */