root-project/root

Error reading custom class object from file with ROOT 6.22,24

Closed this issue · 4 comments

  • Checked for duplicates

Describe the bug

Error reading object from file. Please find a short example to reproduce below.

Minimal example to reproduce

Below I refer to it as test.cpp

#include "map"
#include "array"
#include "iostream"

#include "TObject.h"
#include "TFile.h"

class TestClass : public TObject {
 public:
  TestClass(){
    std::array<std::string, 2> test_array{"aaaa", "bbbbbb"};
    test_map_[test_array] = "cccc";
  }

  void Print(Option_t *option="") const {
    for(const auto& element : test_map_){
      std::cout << element.first[0] << " " << element.first[1] << " " << element.second << std::endl;
    }
  }
 private:
  std::map<std::array<std::string, 2>, std::string> test_map_{};
  ClassDef(TestClass, 1);
};
ClassImp(TestClass)

void test(){
  auto* test_obj = new TestClass;
  test_obj->Print();

  auto* file = TFile::Open("test.root", "recreate");
  test_obj->Write("obj");
  file->Close();

  delete file;
  delete test_obj;

  file = TFile::Open("test.root", "read");
  test_obj = file->Get<TestClass>("obj");
  test_obj->Print();

  file->Close();
  delete file;
}

int main(int argc, char* argv[]) {
  test();
  return 0;
}

Running the example

With a compiled code everything works as expected:

root -l
root [0] .L test.cpp+
root [1] test()

gives correct output:

aaaa bbbbbb cccc
aaaa bbbbbb cccc

But if I try to read again the same file:

root -l test.root
root [0] gSystem->Load("test_cpp")
root [1] obj->Print()
Error in <TBufferFile::ReadVersion>: Could not find the StreamerInfo with a checksum of 0x6b3ba626 for the class "string" in test.root.
Error in <TBufferFile::CheckByteCount>: object of class string read too many bytes: 72 instead of 24
Warning in <TBufferFile::CheckByteCount>: string::Streamer() not in sync with data on file test.root, fix Streamer()
aaaabbbbbb@ cccc�i�� cccc

With an older version of ROOT (6.18), everything works as expected.

Some additional information

I tried to compare StreamerInfo for 2 ROOT versions and they are different (last item):

root 6.18

root [2] _file0->ShowStreamerInfo()
OBJ: TList TList Doubly linked list : 0

StreamerInfo for class: TestClass, version=1, checksum=0x84f55819
TObject BASE offset= 0 type=66 Basic ROOT object
map<array<string,2>,string> test_map_ offset= 0 type=300 (nodelete) ,stl=4, ctype=61,

StreamerInfo for class: pair<array<string,2>,string>, version=1, checksum=0x64321048
string first [2] offset= 0 type=320 ,stl=365, ctype=365,
string second offset= 0 type=300 ,stl=365, ctype=365,

root 6.22,24

  root [3] _file0->ShowStreamerInfo()
  OBJ: TList TList Doubly linked list : 0
  
  StreamerInfo for class: TestClass, version=1, checksum=0x84f55819
  TObject BASE offset= 0 type=66 Basic ROOT object
  map<array<string,2>,string> test_map_ offset= 0 type=300 (nodelete) ,stl=4, ctype=61,
  
  StreamerInfo for class: pair<array<string,2>,string>, version=1, checksum=0xb5fb752
  array<string,2> first offset= 0 type=62 Emulation
  string second offset= 0 type=300 ,stl=365, ctype=365, Emulation
  
  StreamerInfo for class: array<string,2>, version=1, checksum=0x6b3ba626
  string _M_elems offset= 0 type=320 ,stl=365, ctype=365

Unfortunately, I don't how to proceed further.

Setup

  1. Reproduced with ROOT 6.22.08, 6.24 (today's version from the branch with patches)
  2. Operating system Fedora 33 / centos7
  3. binary download / you built it yourself.

The I/O for STL collection containing directly an std::array is not yet supported.
Workaround:

struct StringArray  : public std::array<std::string, 2>
{};
class TestClass : public TObject {
...
 private:
  std::map<StringArray, std::string> test_map_{};
  ClassDef(TestClass, 1);
};

Dear Philippe,

Thank you for the solution!

Is there a way to keep backward compatibility with old files created with ROOT 6.18? With this workaround, I'm getting warnings

Warning in <TStreamerInfo::BuildOld>: Cannot convert test_map_ from type: map<array<string,2>,string> to type: map<StringArray,string>, skip element

Cheers,
Viktor

There is a chance (you need to verify that it reads the data correctly) that adding

#pragma read sourceClass="array<string,2>" targetClass="StringArray";

or

#pragma read sourceClass="map<array<string,2>,string>" targetClass="map<StringArray,string>";

is sufficient.

Given the time that passed since the last comment, I am closing the issue assuming it has been addressed. Of course, I invite the original author to submit a new one related to the same topic in case the problem is not fixed.