Logo ROOT  
Reference Guide
 
Loading...
Searching...
No Matches
RNTupleProcessorDS.cxx
Go to the documentation of this file.
1/// \file RNTupleProcessorDS.cxx
2/// \author Florine de Geus <florine.de.geus@cern.ch>
3/// \author Jakob Blomer <jblomer@cern.ch>
4/// \author Enrico Guiraud <enrico.guiraud@cern.ch>
5/// \date 2026-06-18
6/// \warning This is part of the ROOT 7 prototype! It will change without notice. It might trigger earthquakes. Feedback
7/// is welcome!
8
10#include <ROOT/RDataFrame.hxx>
11#include <ROOT/RDF/Utils.hxx>
12#include <ROOT/RField.hxx>
13#include <ROOT/RFieldUtils.hxx>
17#include <ROOT/RPageStorage.hxx>
18
19#include "RNTupleDSFields.hxx"
20
21#include <TError.h>
22#include <TSystem.h>
23
24#include <cassert>
25#include <memory>
26#include <string>
27#include <string_view>
28
30/// Every RDF column is represented by exactly one RNTuple field
32 RNTupleProcessorOptionalPtr<void> fValuePtr; ///< Container for the value of the column
34
35public:
37 ~RNTupleProcessorColumnReader() override = default;
38
39 void *GetImpl(Long64_t entry) final
40 {
41 assert(entry == fLastLoadedEntry);
42 return fValuePtr.GetPtr().get();
43 }
44
45 void SetEntryLoaded(Long64_t entry) { fLastLoadedEntry = entry; }
46};
47
49
50// TODO: see if common parts of this and RNTupleDS::AddField can be factored out.
51void RNTupleProcessorDS::AddField(const ROOT::RFieldBase &field, std::string_view colName,
53 std::deque<RNTupleProcessorDS::RFieldInfo> &fieldInfos, bool convertToRVec)
54{
55 // As an example for the mapping of RNTuple fields to RDF columns, let's consider an RNTuple
56 // using the following types and with a top-level field named "event" of type Event:
57 //
58 // struct Event {
59 // int id;
60 // std::vector<Track> tracks;
61 // };
62 // struct Track {
63 // std::vector<Hit> hits;
64 // };
65 // struct Hit {
66 // float x;
67 // float y;
68 // };
69 //
70 // AddField() will be called from the constructor with the RNTuple root field (ENTupleStructure::kRecord).
71 // From there, we recurse into the "event" sub field (also ENTupleStructure::kRecord) and further down the
72 // tree of sub fields and expose the following RDF columns:
73 //
74 // "event" [Event]
75 // "event.id" [int]
76 // "event.tracks" [RVec<Track>]
77 // "R_rdf_sizeof_event.tracks" [unsigned int]
78 // "event.tracks.hits" [RVec<RVec<Hit>>]
79 // "R_rdf_sizeof_event.tracks.hits" [RVec<unsigned int>]
80 // "event.tracks.hits.x" [RVec<RVec<float>>]
81 // "R_rdf_sizeof_event.tracks.hits.x" [RVec<unsigned int>]
82 // "event.tracks.hits.y" [RVec<RVec<float>>]
83 // "R_rdf_sizeof_event.tracks.hits.y" [RVec<unsigned int>]
84
85 std::uint16_t nFieldInfosAdded = 0;
86
87 const auto &nRepetitions = field.GetNRepetitions();
88 if ((field.GetStructure() == ROOT::ENTupleStructure::kCollection) || (nRepetitions > 0)) {
89 // The field is a collection or a fixed-size array.
90 // We open a new collection scope with fieldID being the inner most collection. E.g. for "event.tracks.hits",
91 // fieldInfos would already contain the fieldID of "event.tracks"
92 fieldInfos.emplace_back(field.GetOnDiskId(), field.GetFieldName(), field.GetTypeName(), nRepetitions);
93 nFieldInfosAdded++;
94 }
95
97 // Inner fields of collections are provided as projected collections of only that inner field,
98 // E.g. we provide a projected collection RVec<RVec<float>> for "event.tracks.hits.x" in the example
99 // above.
100 bool representableAsRVec =
101 convertToRVec && (field.GetTypeName().substr(0, 19) == "ROOT::VecOps::RVec<" ||
102 field.GetTypeName().substr(0, 12) == "std::vector<" || field.GetTypeName() == "");
103 const auto *f = field.GetConstSubfields()[0];
104 AddField(*f, colName, procProvenance, fieldInfos, representableAsRVec);
105
106 // Note that at the end of the recursion, we handled the inner sub collections as well as the
107 // collection as whole, so we are done.
108 return;
109
110 } else if (nRepetitions > 0) {
111 // Fixed-size array, same logic as ROOT::RVec.
112 const auto *f = field.GetConstSubfields()[0];
113 AddField(*f, colName, procProvenance, fieldInfos);
114 return;
115 } else if (field.GetStructure() == ROOT::ENTupleStructure::kRecord) {
116 // Inner fields of records are provided as individual RDF columns, e.g. "event.id"
117 for (const auto &f : field.GetConstSubfields()) {
118 auto innerName = colName.empty() ? f->GetFieldName() : (std::string(colName) + "." + f->GetFieldName());
119 // Inner fields of collections of records are always exposed as ROOT::RVec
120 AddField(*f, innerName, procProvenance, fieldInfos);
121 }
122
123 // Do not add untyped record fields
124 if (field.GetTypeName() == "")
125 return;
126 }
127
128 // The fieldID could be the root field or the class of fieldId might not be loaded.
129 // In these cases, only the inner fields are exposed as RDF columns.
130 auto fieldOrException = ROOT::RFieldBase::Create(field.GetFieldName(), field.GetTypeName());
131 if (!fieldOrException)
132 return;
133 auto valueField = fieldOrException.Unwrap();
134 if (const auto cardinalityField = dynamic_cast<const ROOT::RCardinalityField *>(valueField.get())) {
135 // Cardinality fields in RDataFrame are presented as integers
136 if (cardinalityField->As32Bit()) {
137 valueField = std::make_unique<ROOT::Internal::RDF::RRDFCardinalityField<std::uint32_t>>(field.GetFieldName());
138 } else if (cardinalityField->As64Bit()) {
139 valueField = std::make_unique<ROOT::Internal::RDF::RRDFCardinalityField<std::uint64_t>>(field.GetFieldName());
140 } else {
141 R__ASSERT(false && "cardinality field stored with an unexpected integer type");
142 }
143 }
144
145 valueField->SetOnDiskId(field.GetOnDiskId());
146 auto valueSubfields = valueField->GetMutableSubfields();
147 const auto fieldSubfields = field.GetConstSubfields();
148 for (unsigned i = 0; i < valueSubfields.size(); ++i) {
149 valueSubfields[i]->SetOnDiskId(fieldSubfields[i]->GetOnDiskId());
150 }
151
152 std::unique_ptr<ROOT::RFieldBase> cardinalityField;
153 // Collections get the additional "number of" RDF column (e.g. "R_rdf_sizeof_tracks")
154 if (!fieldInfos.empty()) {
155 const auto &info = fieldInfos.back();
156 const std::string name =
157 (procProvenance.Empty() ? "R_rdf_sizeof_" : procProvenance.Get() + "_R_rdf_sizeof_") + info.fFieldName;
158 if (info.fNRepetitions > 0) {
159 cardinalityField = std::make_unique<ROOT::Internal::RDF::RArraySizeField>(name, info.fNRepetitions);
160 } else {
161 cardinalityField = std::make_unique<ROOT::Internal::RDF::RRDFCardinalityField<std::size_t>>(name);
162 }
163 cardinalityField->SetOnDiskId(info.fFieldId);
164 }
165
166 for (auto i = fieldInfos.rbegin(); i != fieldInfos.rend(); ++i) {
167 const auto &fieldInfo = *i;
168
169 const auto valueFieldName = valueField->GetFieldName();
170
171 if (fieldInfo.fNRepetitions > 0) {
172 // Fixed-size array, read it as ROOT::RVec in memory
173 valueField =
174 std::make_unique<ROOT::RArrayAsRVecField>(valueFieldName, valueField->Clone("_0"), fieldInfo.fNRepetitions);
175 } else {
176 // Actual collection. A std::vector or ROOT::RVec gets added as a ROOT::RVec. All other collection types keep
177 // their original type.
178 if (convertToRVec) {
179 valueField = std::make_unique<ROOT::RRVecField>(valueFieldName, valueField->Clone("_0"));
180 } else {
181 auto outerFieldType = fieldInfo.fTypeName;
182 valueField = ROOT::RFieldBase::Create(valueFieldName, outerFieldType).Unwrap();
183 }
184 }
185
186 valueField->SetOnDiskId(fieldInfo.fFieldId);
187
188 // Skip the inner-most collection level to construct the cardinality column
189 // It's taken care of by the `if (!fieldInfos.empty())` scope above
190 if (i != fieldInfos.rbegin()) {
191 const auto cardinalityFieldName = cardinalityField->GetFieldName();
192 if (fieldInfo.fNRepetitions > 0) {
193 // This collection level refers to a fixed-size array
194 cardinalityField = std::make_unique<ROOT::RArrayAsRVecField>(
195 cardinalityFieldName, cardinalityField->Clone("_0"), fieldInfo.fNRepetitions);
196 } else {
197 // This collection level refers to an RVec
198 cardinalityField = std::make_unique<ROOT::RRVecField>(cardinalityFieldName, cardinalityField->Clone("_0"));
199 }
200
201 cardinalityField->SetOnDiskId(fieldInfo.fFieldId);
202 }
203 }
204
205 if (cardinalityField) {
206 std::string cardinalityFieldName =
207 (procProvenance.Empty() ? "R_rdf_sizeof_" : procProvenance.Get() + "_R_rdf_sizeof_") + std::string(colName);
208 fColumnNames.emplace_back(cardinalityFieldName);
209 fColumnTypes.emplace_back(cardinalityField->GetTypeName());
210 fProcessor->AddFieldToEntry(std::move(cardinalityField), cardinalityFieldName, nullptr,
212 }
213
214 fieldInfos.emplace_back(field.GetOnDiskId(), field.GetFieldName(), field.GetTypeName(), nRepetitions);
215 nFieldInfosAdded++;
216 std::string canonicalName = (procProvenance.Empty() ? "" : procProvenance.Get() + ".") + std::string(colName);
217 fColumnNames.emplace_back(canonicalName);
218 fColumnTypes.emplace_back(valueField->GetTypeName());
219
220 // Add nested fields explicitly to the entry, because they may be mapped differently.
221 if (fieldInfos.size() > 1) {
222 fProcessor->AddFieldToEntry(std::move(valueField), canonicalName, nullptr,
224 }
225
226 for (std::uint16_t i = 0; i < nFieldInfosAdded; i++) {
227 fieldInfos.pop_back();
228 }
229}
230
231RNTupleProcessorDS::RNTupleProcessorDS(std::unique_ptr<ROOT::Experimental::RNTupleProcessor> processor)
232 : fProcessor(std::move(processor))
233{
234 // Do not add the subfields now, this is handled by AddField.
235 fProcessor->AddAllFieldsToEntry(Internal::RNTupleProcessorProvenance(), /*includeSubfields=*/false);
236 const auto &entry = fProcessor->GetEntry();
237 for (auto fieldIdx : entry.GetFieldIndices()) {
238 const auto &field = entry.GetValue(fieldIdx).GetField();
239 std::deque<RNTupleProcessorDS::RFieldInfo> fieldInfos{};
240 AddField(field, entry.GetQualifiedFieldName(fieldIdx), entry.GetFieldProvenance(fieldIdx), fieldInfos);
241 }
242}
243
245RNTupleProcessorDS::GetColumnReadersImpl(std::string_view /* name */, const std::type_info & /* ti */)
246{
247 // This datasource uses the newer GetColumnReaders() API
248 return {};
249}
250
251std::unique_ptr<ROOT::Detail::RDF::RColumnReaderBase>
252RNTupleProcessorDS::GetColumnReaders(unsigned int /* slot */, std::string_view name, const std::type_info &tid)
253{
254 // At this point we can assume that `name` will be found in fColumnNames
256
257 // First check if a field with the requested type already exists. If that is the case, we can immediately create a
258 // column reader for it.
259 if (fProcessor->GetEntry().FindFieldIndex(name, requestedType)) {
260 auto valuePtr = fProcessor->RequestField(std::string(name), requestedType);
261 auto reader = std::make_unique<Internal::RDF::RNTupleProcessorColumnReader>(valuePtr);
262 fColumnReaders.emplace_back(reader.get());
263
264 return reader;
265 }
266
267 // Secondly, check whether a field with the same name, but a different on-disk type exists. If this is not the case,
268 // first try to add the field. If the field cannot be added, throw an exception.
269 // Otherwise, we create a new field with the requested type and add it to the processor entry
270 // before creating a column reader for it.
271 auto fieldIdx = fProcessor->GetEntry().FindFieldIndex(name);
272 if (!fieldIdx) {
273 std::unique_ptr<Internal::RDF::RNTupleProcessorColumnReader> reader;
274 try {
275 auto valuePtr = fProcessor->RequestField(std::string(name), requestedType);
276 reader = std::make_unique<Internal::RDF::RNTupleProcessorColumnReader>(valuePtr);
277 fColumnReaders.emplace_back(reader.get());
278 } catch (const ROOT::RException &) {
279 throw std::runtime_error("RNTupleProcessorDS: Column \"" + std::string(name) + "\" does not exist");
280 }
281 return reader;
282 }
283
284 const auto &field = fProcessor->GetEntry().GetField(*fieldIdx);
285 const std::string strName = std::string(name);
286 std::unique_ptr<ROOT::RFieldBase> newField;
287 if (dynamic_cast<const ROOT::RCardinalityField *>(&field)) {
288 if (requestedType == "bool") {
289 newField = std::make_unique<ROOT::Internal::RDF::RRDFCardinalityField<bool>>(strName);
290 } else if (requestedType == "char") {
291 newField = std::make_unique<ROOT::Internal::RDF::RRDFCardinalityField<char>>(strName);
292 } else if (requestedType == "std::int8_t") {
293 newField = std::make_unique<ROOT::Internal::RDF::RRDFCardinalityField<std::int8_t>>(strName);
294 } else if (requestedType == "std::uint8_t") {
295 newField = std::make_unique<ROOT::Internal::RDF::RRDFCardinalityField<std::uint8_t>>(strName);
296 } else if (requestedType == "std::int16_t") {
297 newField = std::make_unique<ROOT::Internal::RDF::RRDFCardinalityField<std::int16_t>>(strName);
298 } else if (requestedType == "std::uint16_t") {
299 newField = std::make_unique<ROOT::Internal::RDF::RRDFCardinalityField<std::uint16_t>>(strName);
300 } else if (requestedType == "std::int32_t") {
301 newField = std::make_unique<ROOT::Internal::RDF::RRDFCardinalityField<std::int32_t>>(strName);
302 } else if (requestedType == "std::uint32_t") {
303 newField = std::make_unique<ROOT::Internal::RDF::RRDFCardinalityField<std::uint32_t>>(strName);
304 } else if (requestedType == "std::int64_t") {
305 newField = std::make_unique<ROOT::Internal::RDF::RRDFCardinalityField<std::int64_t>>(strName);
306 } else if (requestedType == "std::uint64_t") {
307 newField = std::make_unique<ROOT::Internal::RDF::RRDFCardinalityField<std::uint64_t>>(strName);
308 } else {
309 throw std::runtime_error("RNTupleProcessorDS: Could not create field with type \"" + requestedType +
310 "\" for column \"" + strName + "\"");
311 }
312 } else {
313 auto baseName = ROOT::Split(strName, ".").back();
314 auto newAltProtoFieldOrException = ROOT::RFieldBase::Create(baseName, requestedType);
315 if (!newAltProtoFieldOrException) {
316 throw std::runtime_error("RNTupleProcessorDS: Could not create field with type \"" + requestedType +
317 "\" for column \"" + strName + "\"");
318 }
319 newField = newAltProtoFieldOrException.Unwrap();
320 }
321 newField->SetOnDiskId(field.GetOnDiskId());
322
323 try {
324 fProcessor->AddFieldToEntry(std::move(newField), strName, nullptr, Internal::RNTupleProcessorProvenance());
325 } catch (const ROOT::RException &) {
326 std::string msg = "RNTupleProcessorDS: invalid type \"" + requestedType + "\" for column \"" + strName +
327 "\" with on-disk type \"" + field.GetTypeName() + "\"";
328 throw std::runtime_error(msg);
329 }
330
331 auto valuePtr = fProcessor->RequestField(std::string(name), requestedType);
332 auto reader = std::make_unique<Internal::RDF::RNTupleProcessorColumnReader>(valuePtr);
333 fColumnReaders.emplace_back(reader.get());
334
335 return reader;
336}
337
338std::vector<std::pair<ULong64_t, ULong64_t>> RNTupleProcessorDS::GetEntryRanges()
339{
340 if (fCurrentRanges.empty()) {
341 fCurrentRanges.emplace_back(0, fProcessor->GetNEntries());
342 return fCurrentRanges;
343 }
344 return {};
345}
346
347void RNTupleProcessorDS::InitSlot(unsigned int /* slot */, ULong64_t /* firstEntry */)
348{
349 assert(fNSlots == 1);
350}
351
352void RNTupleProcessorDS::FinalizeSlot(unsigned int /* slot */)
353{
354 assert(fNSlots == 1);
355}
356
357std::string RNTupleProcessorDS::GetTypeName(std::string_view colName) const
358{
359 auto colNamePos = std::find(fColumnNames.begin(), fColumnNames.end(), colName);
360
361 if (colNamePos == fColumnNames.end()) {
362 auto msg = std::string("RNTupleProcessorDS: There is no column with name \"") + std::string(colName) + "\"";
363 throw std::runtime_error(msg);
364 }
365
366 const auto index = std::distance(fColumnNames.begin(), colNamePos);
367 return fColumnTypes[index];
368}
369
370bool RNTupleProcessorDS::HasColumn(std::string_view colName) const
371{
372 return std::find(fColumnNames.begin(), fColumnNames.end(), colName) != fColumnNames.end();
373}
374
376{
377 fCurrentRanges.clear();
378 fProcessor->Connect(fProcessor->GetEntry().GetFieldIndices(), Internal::RNTupleProcessorProvenance(),
379 /*updateFields=*/false);
380}
381
383{
384 // noop
385}
386
387void RNTupleProcessorDS::SetNSlots(unsigned int nSlots)
388{
389 assert(fNSlots == 0);
390 // FIXME(fdegeus): Set to actual nSlots once MT support lands
391 if (nSlots > 1)
392 Warning("RNTupleProcessorDS", "MT is not yet supported. Using single-threaded processing instead.");
393 fNSlots = 1;
394}
395
396bool RNTupleProcessorDS::SetEntry(unsigned int /* slot */, ULong64_t entry)
397{
398 fProcessor->SetEntryMapping(entry, fEntryMapping);
400 return false;
401
402 fProcessor->LoadEntry(fEntryMapping);
403
404 for (auto colReader : fColumnReaders) {
405 colReader->SetEntryLoaded(entry);
406 }
407
408 return true;
409}
410
412 unsigned int /* slot */, const std::unordered_map<std::string, ROOT::RDF::Experimental::RSample *> &sampleMap) const
413{
414 const auto &ntupleID = fProcessor->GetOptions().GetProcessorName();
415
416 // TODO: There is no support for RNTuple in RDatasetSpec, thus the sample map
417 // is always empty at the moment.
418 if (sampleMap.empty())
419 return ROOT::RDF::RSampleInfo(ntupleID, std::make_pair(0, fProcessor->GetNEntries()));
420
421 if (sampleMap.find(ntupleID) == sampleMap.end())
422 throw std::runtime_error("Full sample identifier '" + ntupleID + "' cannot be found in the available samples.");
423
424 return ROOT::RDF::RSampleInfo(ntupleID, std::make_pair(0, fProcessor->GetNEntries()), sampleMap.at(ntupleID));
425}
426} // namespace ROOT::Experimental::Internal::RDF
427
429ROOT::Experimental::RDF::FromRNTupleProcessor(std::unique_ptr<ROOT::Experimental::RNTupleProcessor> processor)
430{
431 return ROOT::RDataFrame(
432 std::make_unique<ROOT::Experimental::Internal::RDF::RNTupleProcessorDS>(std::move(processor)));
433}
#define f(i)
Definition RSha256.hxx:104
long long Long64_t
Portable signed long integer 8 bytes.
Definition RtypesCore.h:84
unsigned long long ULong64_t
Portable unsigned long integer 8 bytes.
Definition RtypesCore.h:85
#define R__ASSERT(e)
Checks condition e and reports a fatal error if it's false.
Definition TError.h:139
void Warning(const char *location, const char *msgfmt,...)
Use this function in warning situations.
Definition TError.cxx:252
Option_t Option_t TPoint TPoint const char GetTextMagnitude GetFillStyle GetLineColor GetLineWidth GetMarkerStyle GetTextAlign GetTextColor GetTextSize void char Point_t Rectangle_t WindowAttributes_t index
char name[80]
Definition TGX11.cxx:142
Every RDF column is represented by exactly one RNTuple field.
RNTupleProcessorColumnReader(RNTupleProcessorOptionalPtr< void > &valuePtr)
RNTupleProcessorOptionalPtr< void > fValuePtr
Container for the value of the column.
RNTupleProcessorDS(std::unique_ptr< ROOT::Experimental::RNTupleProcessor > processor)
std::string GetTypeName(std::string_view colName) const final
Type of a column as a string, e.g.
void Finalize() final
Convenience method called after concluding an event-loop.
bool HasColumn(std::string_view colName) const final
Checks if the dataset has a certain column.
std::vector< std::pair< ULong64_t, ULong64_t > > GetEntryRanges() final
Return ranges of entries to distribute to tasks.
void FinalizeSlot(unsigned int slot) final
Convenience method called at the end of the data processing associated to a slot.
Record_t GetColumnReadersImpl(std::string_view name, const std::type_info &) final
type-erased vector of pointers to pointers to column values - one per slot
void Initialize() final
Convenience method called before starting an event-loop.
std::vector< ROOT::Experimental::Internal::RDF::RNTupleProcessorColumnReader * > fColumnReaders
std::unique_ptr< ROOT::Detail::RDF::RColumnReaderBase > GetColumnReaders(unsigned int slot, std::string_view name, const std::type_info &) final
If the other GetColumnReaders overload returns an empty vector, this overload will be called instead.
std::vector< std::pair< ULong64_t, ULong64_t > > fCurrentRanges
void InitSlot(unsigned int slot, ULong64_t firstEntry) final
Convenience method called at the start of the data processing associated to a slot.
bool SetEntry(unsigned int, ULong64_t) final
Advance the "cursors" returned by GetColumnReaders to the selected entry for a particular slot.
void AddField(const ROOT::RFieldBase &field, std::string_view colName, Internal::RNTupleProcessorProvenance procProvenance, std::deque< RFieldInfo > &fieldInfos, bool convertToRVec=true)
Provides the RDF column "colName" given the field.
void SetNSlots(unsigned int nSlots) final
Inform RDataSource of the number of processing slots (i.e.
std::string Get() const
Get the full processor provenance, in the form of "x.y.z".
std::shared_ptr< void > GetPtr() const
Get the pointer to the field value managed by the processor's entry.
An artificial field that transforms an RNTuple column that contains the offset of collections into co...
Definition RField.hxx:369
friend ROOT::RDF::RSampleInfo ROOT::Internal::RDF::CreateSampleInfo(const ROOT::RDF::RDataSource &, unsigned int, const std::unordered_map< std::string, ROOT::RDF::Experimental::RSample * > &)
std::vector< void * > Record_t
This type represents a sample identifier, to be used in conjunction with RDataFrame features such as ...
ROOT's RDataFrame offers a modern, high-level interface for analysis of data stored in TTree ,...
Base class for all ROOT issued exceptions.
Definition RError.hxx:78
A field translates read and write calls from/to underlying columns to/from tree values.
ROOT::ENTupleStructure GetStructure() const
std::vector< const RFieldBase * > GetConstSubfields() const
const std::string & GetFieldName() const
static RResult< std::unique_ptr< RFieldBase > > Create(const std::string &fieldName, const std::string &typeName, const ROOT::RCreateFieldOptions &options, const ROOT::RNTupleDescriptor *desc, ROOT::DescriptorId_t fieldId)
Factory method to resurrect a field from the stored on-disk type information.
std::size_t GetNRepetitions() const
const std::string & GetTypeName() const
ROOT::DescriptorId_t GetOnDiskId() const
RDataFrame FromRNTupleProcessor(std::unique_ptr< ROOT::Experimental::RNTupleProcessor > processor)
std::string TypeID2TypeName(const std::type_info &id)
Returns the name of a type starting from its type_info An empty string is returned in case of failure...
Definition RDFUtils.cxx:200
std::string GetRenormalizedTypeName(const std::string &metaNormalizedName)
Given a type name normalized by ROOT meta, renormalize it for RNTuple. E.g., insert std::prefix.
constexpr NTupleSize_t kInvalidNTupleIndex
std::vector< std::string > Split(std::string_view str, std::string_view delims, bool skipEmpty=false)
Splits a string at each character in delims.