Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 74 additions & 0 deletions DataFormats/Common/interface/AnyBuffer.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
#ifndef DataFormats_Common_interface_AnyBuffer_h
#define DataFormats_Common_interface_AnyBuffer_h

#include <type_traits>
#include <typeinfo>
#include <cstddef>

#include <boost/container/small_vector.hpp>

#include "FWCore/Utilities/interface/EDMException.h"
#include "FWCore/Utilities/interface/TypeDemangler.h"

namespace edm {

class AnyBuffer {
public:
AnyBuffer() = default;

template <typename T>
AnyBuffer(T const& t)
requires(std::is_trivially_copyable_v<T>)
: storage_(reinterpret_cast<std::byte const*>(&t), reinterpret_cast<std::byte const*>(&t) + sizeof(T)),
typeid_(&typeid(std::remove_cv_t<T>)) {}

template <typename T>
T& cast_to()
requires(std::is_trivially_copyable_v<T>)
{
if (empty()) {
throw edm::Exception(edm::errors::LogicError)
<< "Attempt to read an object of type " << edm::typeDemangle(typeid(T).name())
<< " from an empty AnyBuffer";
}
if (typeid(std::remove_cv_t<T>) != *typeid_) {
throw edm::Exception(edm::errors::LogicError)
<< "Attempt to read an object of type " << edm::typeDemangle(typeid(T).name())
<< " from an AnyBuffer holding an object of type " << edm::typeDemangle(typeid_->name());
}
return *reinterpret_cast<T*>(storage_.data());
}

template <typename T>
T const& cast_to() const
requires(std::is_trivially_copyable_v<T>)
{
if (empty()) {
throw edm::Exception(edm::errors::LogicError)
<< "Attempt to read an object of type " << edm::typeDemangle(typeid(T).name())
<< " from an empty AnyBuffer";
}
if (typeid(std::remove_cv_t<T>) != *typeid_) {
throw edm::Exception(edm::errors::LogicError)
<< "Attempt to read an object of type " << edm::typeDemangle(typeid(T).name())
<< " from an AnyBuffer holding an object of type " << edm::typeDemangle(typeid_->name());
}
return *reinterpret_cast<T const*>(storage_.data());
}

bool empty() const { return typeid_ == nullptr; }

std::byte* data() { return storage_.data(); }

std::byte const* data() const { return storage_.data(); }

size_t size_bytes() const { return storage_.size(); }

private:
boost::container::small_vector<std::byte, 32> storage_; // arbitrary small vector size to fit AnyBuffer in 64 bytes
std::type_info const* typeid_ = nullptr;
};

} // namespace edm

#endif // DataFormats_Common_interface_AnyBuffer_h
142 changes: 142 additions & 0 deletions DataFormats/Common/interface/TrivialCopyTraits.h
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this file should go under DataFormats/Common ?
The interface described by the trait can also be used independently from the serialisation.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add some unit tests for the traits ?
For example, check that some types (e.g. int or std::vector<float>) are properly supported, that some other types (e.g. std::map) are not supported, and that you can write a simple type and specialise the traits for it.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please recover the previous formatting in the long comment near the beginning of the file.

Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
#ifndef Dataformats_Common_interface_TrivialCopyTraits_h
#define Dataformats_Common_interface_TrivialCopyTraits_h

#include <cassert>
#include <span>
#include <type_traits>
#include <vector>
#include <string>

namespace edm {

// This struct should be specialised for each type that can be safely memcpy'ed.
//
// The specialisation shall have two static methods
//
// static std::vector<std::span<std::byte>> regions(T& object);
// static std::vector<std::span<const std::byte>> regions(T const& object);
//
// that return a vector of address, size pairs. A type that supports this
// interface can be copied by doing a memcpy of all the address, size pairs from a
// source object to a destination object.
//
//
// A specialisation may implement the method properties(), which returns the
// properties of an existing object, which can be used to initialize a newly
// allocated copy of the object via the initialize() method.
//
// using Properties = ...;
// static Properties properties(T const& object);
//
// If properties() is not implemented, the initialize() method takes a single
// argument:
//
// static void initialize(T& object);
//
// If properties() is implemented, the initialize() method should take as a
// second parameter a const reference to a Properties object:
//
// static void initialize(T& object, Properties const& args);
//
//
// A specialisation can optionally provide a static method
//
// static void finalize(T& object);
//
// If present, it should be called to restore the object invariants after a
// memcpy operation.
//

template <typename T>
struct TrivialCopyTraits;

// Checks if the properties method is defined
template <typename T>
concept HasTrivialCopyProperties = requires(T const& object) { TrivialCopyTraits<T>::properties(object); };

// Get the return type of properties(...), if it exists.
template <typename T>
requires HasTrivialCopyProperties<T>
using TrivialCopyProperties = decltype(TrivialCopyTraits<T>::properties(std::declval<T const&>()));

// Checks if the declaration of initialize(...) is consistent with the presence or absence of properties.
template <typename T>
concept HasValidInitialize =
// does not have properties(...) and initialize(object) takes a single argument
(not HasTrivialCopyProperties<T> && requires(T& object) { TrivialCopyTraits<T>::initialize(object); }) ||
// or does have properties(...) and initialize(object, props) takes two arguments
(HasTrivialCopyProperties<T> &&
requires(T& object, TrivialCopyProperties<T> props) { TrivialCopyTraits<T>::initialize(object, props); });

// Checks for const and non const memory regions
template <typename T>
concept HasRegions = requires(T& object, T const& const_object) {
TrivialCopyTraits<T>::regions(object);
TrivialCopyTraits<T>::regions(const_object);
};

// Checks if there is a valid specialisation of TrivialCopyTraits for a type T
template <typename T>
concept HasTrivialCopyTraits =
// Has memory regions declared
(HasRegions<T>) &&
// and has either no initialize(...) or a valid one
(not requires { &TrivialCopyTraits<T>::initialize; } || HasValidInitialize<T>);

// Checks if finalize(...) is defined
template <typename T>
concept HasTrivialCopyFinalize = requires(T& object) { edm::TrivialCopyTraits<T>::finalize(object); };

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Specialisations for various types

Copy link
Author

@ghyls ghyls Oct 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if HasTrivialCopyProperties is a good name for this concept, given that a specialization of TrivialCopyTraits could technically implement properties(...) function without declaring the type alias Properties.

If you find this concept confusing we can eliminate it, as we don't strictly need it.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, in TrivialSerialisation/Common/interface/TrivialSerialiser.h you have

    if constexpr (not requires { typename edm::TrivialCopyTraits<T>::Properties; }) {
      // if edm::TrivialCopyTraits<T>::Properties is not defined, do not call properties()
      return {};

The idea of defining this concept was that those checks could be simplified as

    if constexpr (not HasTrivialCopyProperties<T>) {
      // if edm::TrivialCopyTraits<T>::Properties is not defined, do not call properties()
      return {};

However, I agree with

a specialization of TrivialCopyTraits could technically implement properties(...) function without declaring the type alias Properties.

How about

  • making HasTrivialCopyProperties<T> check for TrivialCopyTraits<T>::properties() instead of TrivialCopyTraits<T>::Properties
  • when TrivialCopyTraits<T>::properties() exists, introduce a new trait TrivialCopyProperties<T> that returns the return type of TrivialCopyTraits<T>::properties()

?

This way people won't need to define Properties, and the generic code can first check based on HasTrivialCopyProperties<T> and then use TrivialCopyProperties<T> to get the type.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

However, we also need a way to enforce that

  • if properties() returns void, then initialize(obj) takes a single argument
  • if properties() returns non-void, then initialize(obj, properties) takes a second argument of the same type

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤔

// Specialisation for arithmetic types
template <typename T>
requires std::is_arithmetic_v<T>
struct TrivialCopyTraits<T> {
static std::vector<std::span<std::byte>> regions(T& object) {
return {{reinterpret_cast<std::byte*>(&object), sizeof(T)}};
}

static std::vector<std::span<const std::byte>> regions(T const& object) {
return {{reinterpret_cast<std::byte const*>(&object), sizeof(T)}};
}
};

// Specialisation for std::string
template <>
struct TrivialCopyTraits<std::string> {
using Properties = std::string::size_type;

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will remove using value_type and using Properties when they alias something small, as they do in these cases.

static Properties properties(std::string const& object) { return object.size(); }
static void initialize(std::string& object, Properties const& size) { object.resize(size); }

static std::vector<std::span<std::byte>> regions(std::string& object) {
return {{reinterpret_cast<std::byte*>(object.data()), object.size() * sizeof(char)}};
}

static std::vector<std::span<const std::byte>> regions(std::string const& object) {
return {{reinterpret_cast<std::byte const*>(object.data()), object.size() * sizeof(char)}};
}
};

// Specialisation for vectors of arithmetic types
template <typename T>
requires(std::is_arithmetic_v<T> and not std::is_same_v<T, bool>)
struct TrivialCopyTraits<std::vector<T>> {
using Properties = std::vector<T>::size_type;

static Properties properties(std::vector<T> const& object) { return object.size(); }
static void initialize(std::vector<T>& object, Properties const& size) { object.resize(size); }

static std::vector<std::span<std::byte>> regions(std::vector<T>& object) {
return {{reinterpret_cast<std::byte*>(object.data()), object.size() * sizeof(T)}};
}

static std::vector<std::span<const std::byte>> regions(std::vector<T> const& object) {
return {{reinterpret_cast<std::byte const*>(object.data()), object.size() * sizeof(T)}};
}
};
} // namespace edm

#endif // Dataformats_Common_interface_TrivialCopyTraits_h
7 changes: 5 additions & 2 deletions DataFormats/Common/interface/Wrapper.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ Wrapper: A template wrapper around EDProducts to hold the product ID.
#include <algorithm>
#include <cassert>
#include <memory>
#include <string>
#include <typeinfo>

namespace edm {
Expand All @@ -38,6 +37,9 @@ namespace edm {
T const* operator->() const { return product(); }

T& bareProduct() { return obj; }
T const& bareProduct() const { return obj; }

void markAsPresent() { present = true; }

//these are used by FWLite
static std::type_info const& productTypeInfo() { return typeid(T); }
Expand Down Expand Up @@ -176,6 +178,7 @@ namespace edm {
}
};
} // namespace soa

template <typename T>
inline std::shared_ptr<edm::soa::TableExaminerBase> Wrapper<T>::tableExaminer_() const {
return soa::MakeTableExaminer<T>::make(&obj);
Expand All @@ -185,4 +188,4 @@ namespace edm {

#include "DataFormats/Common/interface/WrapperView.icc"

#endif
#endif // DataFormats_Common_Wrapper_h
Loading