diff --git a/tree/ntuple/v7/inc/ROOT/RNTupleReader.hxx b/tree/ntuple/v7/inc/ROOT/RNTupleReader.hxx index 71e00fda86464..65c7f899d4cfd 100644 --- a/tree/ntuple/v7/inc/ROOT/RNTupleReader.hxx +++ b/tree/ntuple/v7/inc/ROOT/RNTupleReader.hxx @@ -284,6 +284,22 @@ public: return GetView(RetrieveFieldId(fieldName), rawPtr); } + /// Provides access to an individual (sub)field, reading its values into `rawPtr` as the type provided by `typeName`. + /// + /// \sa GetView(std::string_view, std::shared_ptr) + ROOT::RNTupleView GetView(std::string_view fieldName, void *rawPtr, std::string_view typeName) + { + return GetView(RetrieveFieldId(fieldName), rawPtr, typeName); + } + + /// Provides access to an individual (sub)field, reading its values into `rawPtr` as the type provided by `ti`. + /// + /// \sa GetView(std::string_view, std::shared_ptr) + ROOT::RNTupleView GetView(std::string_view fieldName, void *rawPtr, const std::type_info &ti) + { + return GetView(RetrieveFieldId(fieldName), rawPtr, ROOT::Internal::GetRenormalizedDemangledTypeName(ti)); + } + template ROOT::RNTupleView GetView(ROOT::DescriptorId_t fieldId) { @@ -295,6 +311,7 @@ public: template ROOT::RNTupleView GetView(ROOT::DescriptorId_t fieldId, std::shared_ptr objPtr) { + static_assert(!std::is_void_v, "invalid attempt to call GetView without a type name"); auto field = ROOT::RNTupleView::CreateField(fieldId, *fSource); auto range = ROOT::Internal::GetFieldRange(*field, *fSource); return ROOT::RNTupleView(std::move(field), range, objPtr); @@ -303,11 +320,32 @@ public: template ROOT::RNTupleView GetView(ROOT::DescriptorId_t fieldId, T *rawPtr) { + static_assert(!std::is_void_v, "invalid attempt to call GetView without a type name"); auto field = ROOT::RNTupleView::CreateField(fieldId, *fSource); auto range = ROOT::Internal::GetFieldRange(*field, *fSource); return ROOT::RNTupleView(std::move(field), range, rawPtr); } + /// Provides access to an individual (sub)field from its on-disk ID, reading its values into `rawPtr` as the type + /// provided by `typeName`. + /// + /// \sa GetView(std::string_view, std::shared_ptr) + ROOT::RNTupleView GetView(ROOT::DescriptorId_t fieldId, void *rawPtr, std::string_view typeName) + { + auto field = RNTupleView::CreateField(fieldId, *fSource, typeName); + auto range = ROOT::Internal::GetFieldRange(*field, *fSource); + return RNTupleView(std::move(field), range, rawPtr); + } + + /// Provides access to an individual (sub)field from its on-disk ID, reading its values into `objPtr` as the type + /// provided by `ti`. + /// + /// \sa GetView(std::string_view, std::shared_ptr) + ROOT::RNTupleView GetView(ROOT::DescriptorId_t fieldId, void *rawPtr, const std::type_info &ti) + { + return GetView(fieldId, rawPtr, ROOT::Internal::GetRenormalizedDemangledTypeName(ti)); + } + template ROOT::RNTupleDirectAccessView GetDirectAccessView(std::string_view fieldName) { diff --git a/tree/ntuple/v7/inc/ROOT/RNTupleView.hxx b/tree/ntuple/v7/inc/ROOT/RNTupleView.hxx index f835cd77cb167..a2041fa60f940 100644 --- a/tree/ntuple/v7/inc/ROOT/RNTupleView.hxx +++ b/tree/ntuple/v7/inc/ROOT/RNTupleView.hxx @@ -88,15 +88,19 @@ protected: ROOT::RNTupleGlobalRange fFieldRange; ROOT::RFieldBase::RValue fValue; - static std::unique_ptr - CreateField(ROOT::DescriptorId_t fieldId, ROOT::Experimental::Internal::RPageSource &pageSource) + static std::unique_ptr CreateField(ROOT::DescriptorId_t fieldId, + Experimental::Internal::RPageSource &pageSource, + std::string_view typeName = "") { std::unique_ptr field; { const auto &desc = pageSource.GetSharedDescriptorGuard().GetRef(); const auto &fieldDesc = desc.GetFieldDescriptor(fieldId); if constexpr (std::is_void_v) { - field = fieldDesc.CreateField(desc); + if (typeName.empty()) + field = fieldDesc.CreateField(desc); + else + field = ROOT::RFieldBase::Create(fieldDesc.GetFieldName(), std::string(typeName)).Unwrap(); } else { field = std::make_unique>(fieldDesc.GetFieldName()); } diff --git a/tree/ntuple/v7/test/ntuple_view.cxx b/tree/ntuple/v7/test/ntuple_view.cxx index 0ba1482ca71ca..0b8073d245478 100644 --- a/tree/ntuple/v7/test/ntuple_view.cxx +++ b/tree/ntuple/v7/test/ntuple_view.cxx @@ -274,12 +274,6 @@ TEST(RNTuple, ViewWithExternalAddress) auto view_1 = reader->GetView("pt", data_1); view_1(0); EXPECT_FLOAT_EQ(42.0, *data_1); - - // Void shared_ptr - std::shared_ptr data_2{new float()}; - auto view_2 = reader->GetView("pt", data_2); - view_2(0); - EXPECT_FLOAT_EQ(42.0, *static_cast(data_2.get())); } TEST(RNTuple, BindEmplaceTyped) @@ -341,7 +335,7 @@ TEST(RNTuple, BindEmplaceVoid) // bind to shared_ptr std::shared_ptr value1{new float()}; - auto view = reader->GetView("pt", nullptr); + auto view = reader->GetView("pt"); view.Bind(value1); view(0); EXPECT_FLOAT_EQ(11.f, *reinterpret_cast(value1.get())); @@ -433,7 +427,7 @@ TEST(RNTuple, ViewFrameworkUse) for (auto i : reader->GetEntryRange()) { if (i > 1) { if (!viewPx) { - viewPx = reader->GetView("px", &px); + viewPx = reader->GetView("px", &px, "float"); } (*viewPx)(i); EXPECT_FLOAT_EQ(i, px); @@ -441,7 +435,7 @@ TEST(RNTuple, ViewFrameworkUse) if (i > 3) { if (!viewPy) { - viewPy = reader->GetView("py", &py); + viewPy = reader->GetView("py", &py, "float"); } (*viewPy)(i); EXPECT_FLOAT_EQ(0.2 + i, py); @@ -449,7 +443,7 @@ TEST(RNTuple, ViewFrameworkUse) if (i > 7) { if (!viewPz) { - viewPz = reader->GetView("pz", &pz); + viewPz = reader->GetView("pz", &pz, "float"); } (*viewPz)(i); EXPECT_FLOAT_EQ(0.4 + i, pz); @@ -533,3 +527,83 @@ TEST(RNTuple, ViewFieldIteration) EXPECT_THAT(err.what(), testing::HasSubstr("field iteration over empty fields is unsupported")); } } + +TEST(RNTuple, VoidWithExternalAddressAndTypeName) +{ + FileRaii fileGuard("test_ntuple_voidwithexternaladdressandtypename.root"); + + { + auto model = RNTupleModel::Create(); + auto fldFoo = model->MakeField("foo"); + auto fldBar = model->MakeField("bar"); + auto fldBaz = model->MakeField>("baz"); + + auto writer = RNTupleWriter::Recreate(std::move(model), "ntpl", fileGuard.GetPath()); + *fldFoo = 42; + *fldBar = std::numeric_limits::max(); + *fldBaz = {1.f, 2.f, 3.f}; + writer->Fill(); + } + + auto reader = RNTupleReader::Open("ntpl", fileGuard.GetPath()); + ASSERT_EQ(1u, reader->GetNEntries()); + + // Read "foo" as std::int32_t (instead of its on-disk type std::uint64_t) + auto int32SharedPtr = std::make_shared(); + + // Raw pointer interface from type name string + { + auto viewFoo = reader->GetView("foo", int32SharedPtr.get(), "std::int32_t"); + viewFoo(0); + EXPECT_EQ(42, *int32SharedPtr); + EXPECT_STREQ("std::int32_t", viewFoo.GetField().GetTypeName().c_str()); + } + + // Raw pointer interface from std::type_info + { + auto viewFoo = reader->GetView("foo", int32SharedPtr.get(), typeid(std::int32_t)); + viewFoo(0); + EXPECT_EQ(42, *int32SharedPtr); + EXPECT_STREQ("std::int32_t", viewFoo.GetField().GetTypeName().c_str()); + } + + // Reading as a type when the on-disk value doesn't fit in this type is not possible + try { + auto viewBar = reader->GetView("bar", int32SharedPtr.get(), "std::int32_t"); + viewBar(0); + } catch (const ROOT::RException &err) { + EXPECT_THAT(err.what(), testing::HasSubstr("value out of range: 18446744073709551615 for type i")); + } + + // Read "baz" as std::vector (instead of its on-disk type std::vector) + std::vector doubleVec; + void *doubleVecPtr = &doubleVec; + std::vector expDoubleVec{1., 2., 3.}; + + // From type name string + { + auto viewBaz = reader->GetView("baz", doubleVecPtr, "std::vector"); + viewBaz(0); + EXPECT_EQ(expDoubleVec, viewBaz.GetValue().GetRef>()); + EXPECT_STREQ("baz", viewBaz.GetField().GetFieldName().c_str()); + EXPECT_STREQ("std::vector", viewBaz.GetField().GetTypeName().c_str()); + } + + // From std::type_info + { + auto viewBaz = reader->GetView("baz", doubleVecPtr, typeid(std::vector)); + viewBaz(0); + EXPECT_EQ(expDoubleVec, viewBaz.GetValue().GetRef>()); + EXPECT_STREQ("baz", viewBaz.GetField().GetFieldName().c_str()); + EXPECT_STREQ("std::vector", viewBaz.GetField().GetTypeName().c_str()); + } + + try { + std::vector intVec; + void *bazAsIntsPtr = &intVec; + reader->GetView("baz", bazAsIntsPtr, "std::vector"); + } catch (const ROOT::RException &err) { + EXPECT_THAT(err.what(), testing::HasSubstr("On-disk column types {`SplitReal32`} for field `baz._0` cannot be " + "matched to its in-memory type `std::int32_t`")); + } +}