Error when using non-template version of Branch with std::array

I’m currently seeing an error in TTree::Branch for std::array. With this simple reproducer:

#include "TFile.h"
#include "TTree.h"
#include <array>
int main(){
  std::array<float, 5> a;
  TFile *f = TFile::Open("test.root", "RECREATE");
  TTree *t = new TTree("t","t");
  t->Branch("a", "array<float,5ul>", (void*)(&a));
  t->Fill();
  t->Write();
  f->Close();
  delete f;
  return 0;
}

I get this:

Error in <TBranchElement::InitializeOffsets>: Could not find the real data member '_M_elems[5]' when constructing the branch 'a' [Likely missing ShowMember].

The reproducer is a simplified version of a general-purpose code that creates a branch by querying the class name at runtime, which ideally should work for any kind of class, and which gives the above error when used with a std::array.

By replacing the Branch call with:

t->Branch("a", &a);

the error vanishes, the actual branch type becomes float[5], and everything works as expected even when reading the branch out using a std::array buffer. Unfortunately I cannot use this template overload since the object type has to be determined at run time in the real general-purpose code.

So my question is: is it possible to obtain the correct behavior using an overload of Branch that determines the type at run time?

The syntax you use is not yet implemented for std::array, in the meantime you need a work-around. You need to transform the string "array<float,5ul>" into "somename[5]/F"

The inner code we use is:

      TString varname;
      varname.Form("%s[%d]/%c", branchname, (int)N, ROOT::Internal::RDF::TypeName2ROOTTypeName(datatype));
      return Branch(branchname, addobj, varname.Data(), bufsize);

where you would use datatype == "float" and N == 5 in your example.

Thanks for the tip, Philippe. In another thread you mentioned that there are other types (i.e. std::pair, std::tuple, std::unique_ptr) that features a special treatment regarding I/O, if I correctly understood your comment. Is there any reference about how to treat them in this context? I guess I can reverse-engineer it but documentation would be preferable.

std::pair and std::ntuple are special in that their dictionary is generated implicitly. When creating branches there is nothing different/unusual to do.

std::unique_ptr is special is that it is treated by the I/O (in most cases) as a raw pointer. The impact on using std::unique_ptr vs a raw pointer are the same, the lifetime of the pointer variable needs to be longer than the lifetime of the tree.

So in short no extra special treatement.

@pcanal One last question related to this topic: if I’m correct there’s no way at the moment to create a branch holding a std::array<SomeClass,N>, right? If I try like this:

std::array<TH1F,5> ah;
t.Branch("b", &ah);

I get this:

Error in <TTree::Branch>: std::array of objects not yet supported as top level branch object (the class is array<TH1F,5>)

Is there any plan to support this case in future releases? I’m using 6.26/04.

We are unlikely to add it as we are focusing development effort on RNTuple. Note that you can use std::vector<TH1F> (albeit it is less storage efficient) or wrap the array in a struct for which you generate the dictionary.

Understood, thank you!

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