Loading custom class into CINT: "declared but not defined"


ROOT Version: 5.28
Platform: CentOS 7
Compiler: g++4.8.5


Hello ROOTers.
I am stuck using ROOT 5, and having some trouble making ROOT CINT aware of my custom class.
I’ve compiled the class definition into a shared library, and done the following:

gSystem->Load("/mycode/lib/libStore.so")
gInterpreter->AddIncludePath("/mycode/include")
gInterpreter->ProcessLine("#include \"MyClass.h\"")
[... a few compiler warnings, just -Wsign-compare ... ]
MyClass myclass;
Error: MyClass() declared but not defined (tmpfile):1:
*** Interpreter error recovered ***

or sometimes (as usual i’ve tried many permutations at this point and somewhat lost track of things)

Error: Can't call MyClass::Set("myint",myint) in current scope (tmpfile):1:
Possible candidates are...
*** Interpreter error recovered ***

My class has templated functions, and I have managed to get it working by including the following (based on this thread):

#ifdef __CINT__
  #pragma link C++ function MyClass::Set(std::string, int);
  #pragma link C++ function MyClass::Set(std::string, double);
#else
  template bool MyClass::Set(std::string, int);
  template bool MyClass::Set(std::string, double);
#endif

This allows me to call Set<int> and Set<double> … but I would really like to be able to call Set<anything>. I’ve seen other threads that seem to suggest templating ought to work.

I also tried hiding the implementation of the method from CINT as suggested here, by doing:

#ifdef __CINT__
	template<typename T> bool Set(std::string, T in);
#else
	template<typename T>
	bool Set(std::string, T in){
		/* implementation */
		return true;
	}
#endif

but that didn’t seem to help - I get

Error: Can't call MyClass::Set("myint",myint) in current scope (tmpfile):1:
Possible candidates are...
(in MyClass)
*** Interpreter error recovered ***

if i don’t include the #pragma lines, and Warning: #pragma link, function Set(std::string,int) not found MyClass.h if I do. (i also tried MAKECINT instead of __CINT__with the same result.)
I also tried building a dictionary for my class, and loading that as well (though i’m not sure if i need it? I have no need to write the objects to file), but with no change.

MyClass.h (1.2 KB)
tester.cpp (1.1 KB)
Here are my little tester files.

Maybe @pcanal can help you with this

I did a little more fiddling and realised my reproducer wasn’t working, while my code with the original class was, and found there’s a small “fix” required to my ‘MyClass.h’ reproducer: the Get and Set methods need to assign a name to the first (unused) string argument, i.e.

template <typename T> bool Set(std::string, T in)

should be

template <typename T> bool Set(std::string name, T in)

otherwise the gInterpreter complains symbol 'in' not defined.

With that fix, MyClass works when the #pragma lines are included, both constructing an instance from the gInterpreter, or passing it a pointer to one made outside it, but fails when the pragma lines aren’t included in both cases at the Set/Get calls with Error: Can't call MyClass::Set("anint",anint) in current scope (tmpfile).

This behaviour is a little different from the actual full class i’m trying to work with, which fails earlier (on declaration, before even getting to the Set call) with class declared but not defined when the #pragma lines are not included.

You might want to try adding:

  #pragma link C++ function MyClass::Set(const char*, int);
  #pragma link C++ function MyClass::Set(const char*, double);

as it is very plausible that CINT is not getting the ‘conversion + overload resolution’ correct.

Hi @pcanal,
thanks for the reply. Maybe the thread was a little unclear, as i’ve been discussing slightly different behaviour depending on how I try to approach things.
The crux of the matter is that I’m using a template because the accepted type could really be anything - any fundamental type, stl containers of types… (we’ll not mention custom classes yet, but …).
Having to specify every possible scenario in #pragma lines blows up much too quickly - it effectively makes the whole concept a no-go.

However! If i simply do:

marc@sandbox:~/mycode$ root
root [0] .L MyClass.h
root [1] MyClass me
root [2] int thing=99
root [3] std::string name="potato"
root [4] me.Set(name,thing);

it works without any problems!.. with my minimal test case. My actual class is a bit more complicated,and returns
Error: BStore() declared but not defined (tmpfile):1:
when i try to instantiate an instance.

More likely, I’m going to want to compile my complicated class and load it via a library - however, after doing so even my minimal reproducer doesn’t work unless I include #pragma lines for every type I want to use. Which, as I say, I would like to avoid.

humm … Maybe you can try to

  • drop the #pragma
  • unhide the implementation from CINT

Note, TTree has the exact case you described (compiled class with dictionary with templated member function that can (and is) instantiation with any type). See TTree::Branch and TTree::SetBranchAddress

humm … oups … it was not trivial to implement see commit b4cbed0db2cd79220ac40fa892885a883b31a26b (and in particular [tree/]tree/src/ManualTree2.cxx)

Encouraging to hear that it’s possible! Unfortunately, I currently don’t have any implementation hidden from CINT (i’ve read it suggested many times, but with no idea about the logic of it).
And, without the #pragma, it does not work (current situation).
I checked the sourcefiles for TTree in ROOT 5.28 but can’t see any templated functions at all…? Are you perhaps thinking of ROOT 6?

!?!? :scream:

At least consider upgrading to v5.34 …

:joy: i know. Believe me, were it an option… it was so easy in ROOT 6!
But, it’s what the collaboration uses. :pensive:

but even v5-28 has them:

mac-135395:rootcling (pairOffset) pcanal$ git grep template v5-28-00 tree/tree/inc/TTree.h
v5-28-00:tree/tree/inc/TTree.h:      // Overload to avoid confusion between this signature and the template instance.
v5-28-00:tree/tree/inc/TTree.h:      // Overload to avoid confusion between this signature and the template instance.
v5-28-00:tree/tree/inc/TTree.h:      // Overload to avoid confusion between this signature and the template instance.
v5-28-00:tree/tree/inc/TTree.h:   template <class T> TBranch *Branch(const char* name, const char* classname, T* obj, Int_t bufsize = 32000, Int_t splitlevel = 99)
v5-28-00:tree/tree/inc/TTree.h:   template <class T> TBranch *Branch(const char* name, const char* classname, T** addobj, Int_t bufsize = 32000, Int_t splitlevel = 99)
v5-28-00:tree/tree/inc/TTree.h:   template <class T> TBranch *Branch(const char* name, T** addobj, Int_t bufsize = 32000, Int_t splitlevel = 99)
v5-28-00:tree/tree/inc/TTree.h:   template <class T> TBranch *Branch(const char* name, T* obj, Int_t bufsize = 32000, Int_t splitlevel = 99)
v5-28-00:tree/tree/inc/TTree.h:   template <class T> Int_t SetBranchAddress(const char *bname, T **add, TBranch **ptr = 0) {
v5-28-00:tree/tree/inc/TTree.h:   // This can only be used when the template overload resolution can distringuish between
v5-28-00:tree/tree/inc/TTree.h:   template <class T> Int_t SetBranchAddress(const char *bname, T *add, TBranch **ptr = 0) {

The transition from v5.28 to v5.34 “should” not be too harsh, it still uses CINT

but even v5-28 has them

you’re right, i jumped straight to the src/ directory and looked at TTree::Branch.

The transition from v5.28 to v5.34 “should” not be too harsh, it still uses CINT

My concern is more that the shared computing resources have a central installation of ROOT, and it is used by everyone and everything. The project I’m working on is intended to be widely applicable (hence the requirement for general types) so having it require a different ROOT version to be used is probably not possible.

If TTree is able to do it in ROOT 5.28, though, it is possible! If it can be done once…!

the shared computing resources have a central installation of ROOT,

I was indeed suggesting to push the whole experiment to move up :slight_smile:

:sweat_smile: i think that decision is above my pay grade

OK i’ve had a look over the implementation in TTree::SetAddress. While a lot of the code is over my head (as expected), my understanding is the following:
A templated version of the setAddress method with signature (char*, T** add, TBranch*) is defined in the header.

template <class T> Int_t SetBranchAddress(const char *bname, T **add, TBranch **ptr = 0)

Notably, all this actually does in terms of templated functionality is call typeid(T)!

Perhaps also relevant, there is then another method with a similar signature hidden from CINT where the templated type is simply replaced by a void*? I’m not sure of the significance of this or if it’s unrelated.

#if !defined(__CINT__)
virtual Int_t           SetBranchAddress(const char *bname,void *add, TBranch **ptr = 0);
#endif

The real secret sauce seems to be in ManualTree2Body.h, which defines a function that “emulates” the templated function:

static int G__ManualTree2_126_0_190(G__value* result7, G__CONST char* funcname, struct G__param* libp, int hash)

The signature doesn’t match, so I don’t quite understand it, but presumably an interpreted call to the templated version somehow gets forwarded to this. The trick then seems to be to have this function somehow carry out the required operations using G__param to provide information on the called type, and take action accordingly. i.e. it does

TypeInfo_t *ti = gInterpreter->TypeInfo_Factory( & libp->para[1] );
string type( TClassEdit::ShortType( gInterpreter->TypeInfo_Name( ti ) ,TClassEdit::kDropTrailStar) );
TClass *ptrClass = TClass::GetClass(type.c_str());
TDataType *data = gROOT->GetType(type.c_str());
EDataType datatype = data ? (EDataType)data->GetType() : kOther_t;

to get something that may be used in place of the output of typeid(T)?

Does that sound correct?
My concern with this system is, as far as I can tell, the code never interacts with the object - it just inspects its type information, and takes some generic action. For instance, it never calls a member function from the object. This is something I would need to do (e.g. calling operator>> among other things). I have no idea whether or how it would be possible to do this sort of thing just using just passive code and the output of typeid, effectively.

Am I missing something, or is this a dead end?

I do see in the CINT manual some stuff regarding using ROOT::Reflex to do runtime object interaction using e.g.

Reflex::Type t = Reflex::Type::ByName("TH1D");
Reflex::Object o = t.Construct();
o.Invoke("Dump");

but I’d need a bit of help with that - can one go from TypeInfo_t to Reflex::Type? Rather than Construct, can one get a Reflex::Object for an object that already exists? Why doesn’t the above code even work?
It also seems like a lot of work. :disappointed:

CINT mechanism: What the manual dictionary entry does is inject in CINT a member function with a prototype described in another part. Namely the signature is that of the void* member function you notice. The interesting part is that when CINT “invokes” the member functions, it will actually call the (trampoline) function G__ManualTree2_126_0_190 and even-though the parameter is technically a void*, in the CINT data structure the type provided by the user is preserved.

The code (with TypeInfo_Factory) converts this information into the ROOT meta class (i.e. TClass and TDataType) so that there can be further manipulated.

In the compiled SetBranchAddress implemantation, the code does something similar, it takes the result of typeid and converts into the ROOT meta class.

Subsequently G__ManualTree2_126_0_190 and the compiled SetBranchAddress call the same member function that uses the ROOT meta class to do the ‘right’ thing based in the type.

The Branch and SetBranchAddress implementation then use the ROOT meta class to do the “right” things

  • Understand the content of the object via the TClass (including the list of data members)
  • Selection the right I/O action based on the content of TClass::GetStreamerInfo()
  • Create object via TClass:New
  • Could (TTree::Draw does) call members function (starting from TClass::GetClassMethod*

to get something that may be used in place of the output of typeid(T)?

So indeed you are right.

This is something I would need to do (e.g. calling operator>> among other things). I have no idea whether or how it would be possible to do this sort of thing

The simplest is to generate code for CINT to interpret. Something like:

TString invoke;
invoke.Form("*(std::ostream*)%p << *(%s)%p;", (void*)G__int(libp->para[0]), (void*)G__int(libp->para[1]));
gROOT->ProcessLine(invoke);

(I probably got the parameter numbering incorrect.)
(there is way to grab the result if it needs to be returned).

OK, thanks for the outline. It sounds like my interpretation was largely correct. I guess the only part that gave me pause is where you say:

it will actually call the (trampoline) function G__ManualTree2_126_0_190 and even-though the parameter is technically a void*, in the CINT data structure the type provided by the user is preserved.

Perhaps it’s splitting hairs, but I would say the type information is preserved (as components in the G__param* libp struct), rather than the type is preserved - otherwise G__ManualTree2_126_0_190 could simply call typeid(libp->para[1]) in the same way the templated function in the header does (assuming libp->para[1] was the type… :man_shrugging:). Is that correct?

On your suggestion of using ProcessLine… do you mean that within the trampoline function, where a concrete type is represented by a member of libp, to replicate the functionality of my templated function with a series of ProcessLine calls that use the runtime type name? e.g.

TString invoke= TString::Format("%s* passed_var = (%s *)0x%x",libp->pars[0],libp->pars[1])";
gROOT->ProcessLine(invoke);
invoke = "for(int i=0; i<passed_var->size(); ++i){";
gROOT->ProcessLine(invoke);
//... etc. etc, rest of what MyClass::Set is doing

… I suppose that might work… but it would be too much to implement in practice (the real MyClass:Set has templated overloads, calls other templated member functions, including templated member functions from other classes… etc)…

=======================================================

I draw a line here, as I think I have moved forward. (continued in next post…)