Call C++ function with vector argument from PyROOT

Dear enthusiasts,

Apologies in advance for my use of pseudo-code below, but I hope it conveys what I want to achieve clearly enough.

I have a custom C++ class with a template (called TData) function that I would like to run from PyROOT that takes a vector of strings as argument names to be processed:

// MyHelper.h
struct MyHelper
{
template <typename T>
void myAction(const std::string& name, const std::vector<std::string>& args);
};
...
// myCode.cxx
auto helper = MyHelper();
helper.myAction<TData>("doSomething",{"thing1","thing2"}); // great! want to do this in PyROOT

Via PyROOT, I would like to be able to call this as “minimal” as possible:

# myCode.py
import ROOT
helper = ROOT.MyHelper()
helper.myAction[ROOT.TData]("doSomething",['thing1','thing2'])
# Unfortunately not possible, as conversion from list to vector is ill-formed?

The basic issue here I face here is that I would like to be able to supply a simple list/tuple as the second argument, which isn’t possible. The workarounds that I’ve considered are:

  1. template parameter pack arguments
// MyHelper.h
struct MyHelper
{
// use template paramter pack instead of vector
template <typename T, typename... Args>
void myAction(const std::string& name, Args... args);
};
# myCode.py
helper.myAction[ROOT.TData]("doSomething",'thing1','thing2')
# This would be nice, PyROOT template parameter deduction does not seem to quite work reliably here
helper.myAction[ROOT.TData,ROOT.std.string,ROOT.std.string]("doSomething",'thing1','thing2')
# This works, but ends up being very verbose
  1. a python function to convert list to vector
helper.myAction[ROOT.TData]("doSomething",vectorize(['thing1','thing2']))
# Annoying to have to wrap vectorize() everytime...
  1. #2 + Pythonization of the class
# MyHelper.py
class MyHelper(ROOT.MyHelper):
  def __init__(self):
    ROOT.MyHelper.__init__(self)
  # re-define myAction to call the base class one with proper arguments
  def myAction(self,dataType):  # provide dataType as first function argument
    def myActionWithDataType(name,args):  # nested function accepting the name and *list* of arguments
      ROOT.MyHelper.myAction[dataType](self,name,vectorize(args))  # vectorization of list from #1
    return myActionWithDataType

# myCode.py
helper = MyHelper.MyHelper()
helper.myAction(ROOT.TData)("doSomething",["thing1","thing2"])
# This achieves the minimal interface that I want as long as I provide such a python wrapper for every single function that I write.
# Is it bad to "regress" to using the () brackets instead of [] for the template parameter?

An analogous discrepancy in C++/PyROOT functionality I can find is the RDataFrame::Define() method, where in C++ one can supply a vector of strings as column names to be processed as arguments to the definition, but from PyROOT one can only call the one with an expression and no column names.

So far, #3 feels 95% satisfactory for me, the nitpick being that I am using () brackets instead of [] as they would ideally be for the template parameter. Would there be a better way that I might have missed, e.g. way to keep the [] brackets in the pythonized function?


Please read tips for efficient and successful posting and posting code

ROOT Version: 6.20.06


I’m sure @etejedor can help you

I think the best approach I’ve come up with so far is a fancier version of #3:

# wrapper around action
class MyAction:
  def __init__(self,helper):
    self.helper = helper
  def __getitem__(self,tmpl):
    self.tmpl = tmpl  # calling [] on this object saves the template parameter for later
    return self.myAction  # return a function that calls the actual C++ of the helper instance
  def myAction(self,name,args):
    # at this point, I know what template parameter to provide AND can massage the arguments however I need when calling the actual C++ function
    return super(ROOT.MyHelper,self.helper).myAction[self.tmpl](name,vectorize(args))

# wrapper around helper
class MyHelper(ROOT.MyHelper):
  def __init__(self):
    ROOT.MyHelper.__init__(self)
    self.myAction = MyAction(self) # assign the "function" as the above wrapper class

myHelper = MyHelper()
myHelper.myAction[ROOT.TData]("doSomething",["thing1","thing2"])
# cosmetically achieves exactly the same thing as I could have in C++

Hello,

I think your last example is what looks better, since you keep the original syntax.

On the other hand, I’d like to understand what prevents to do this without a pythonization. For example, this works:

import ROOT

ROOT.gInterpreter.Declare("""
// MyHelper.h
struct MyHelper
{
template <typename T>
void myAction(const std::string& name, const std::vector<std::string>& args) { cout << "Called myAction" << endl; }
};
""")

helper = ROOT.MyHelper()
helper.myAction['int']("doSomething",['thing1','thing2'])

So I guess the problem is that TData type? Can you share the definition of that type?

Hi @etejedor,

Much apologies for the confusion: I was mistaken about my ROOT release that my analysis framework uses, which is 6.20.06, which is giving me this error:

TypeError: void Test::test(const string& name, const vector<string>& args) =>
    could not convert argument 2

But it works for 6.24.06 – could you confirm this is indeed expected behaviour between the two versions? I’m a bit surprised that support for this is relatively recent (if 6.20.06 can be considered that…). Also I can report that the partial template parameter deduction as implemented by #2 works reliably under the new release. So really either of my vanilla approaches work seamlessly, rendering the issue essentially irrelevant.

Since my code is based on ATLAS framework, I am stuck with the old ROOT release and thus the pythonization wrappers for the time being if I really want things to look syntactically neat :frowning:

P.S. TData is mostly built-in types so I don’t see any issue with this

Hello @taehyounpark ,

Indeed there was a big change for PyROOT in 6.22, now it’s implemented on top of the cppyy bindings. One of the new things that cppyy supports for type conversion is list → std::vector.

So I’d suggest you keep your idea of the pythonization for now and you get rid of it once you are able to move to a newer ROOT version.

You basically found a solution yourself but let me know if I can help with anything else :slight_smile:

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.