Как преобразовать std :: vector уникальных указателей в std :: span необработанных указателей? - PullRequest
0 голосов
/ 03 февраля 2020

У меня есть следующая функция в интерфейсе некоторого модуля:

void DoSomething(Span<MyObject *const> objects);

, где Span - моя упрощенная реализация шаблона C ++ 20's std::span.

Эта функция просто перебирает непрерывную последовательность указателей на объекты и вызывает некоторые из их функций, не пытаясь изменить указатели (таким образом, const в сигнатуре).

На стороне вызывающей стороны у меня есть std::vector<std::unique_ptr<MyObject>>. И я хочу передать этот вектор в DoSomething функцию без выделения дополнительной памяти (для чего-то вроде временного std::vector<MyObject*>). Я просто хочу преобразовать вектор lvalue unique_ptr s в Span неизменяемых необработанных указателей в постоянное время.

Это должно быть возможно, потому что std::unique_ptr<T> с удалителем без сохранения состояния имеет тот же размер и выравнивание как необработанный указатель T*, и все, что он хранит внутри, есть не что иное, как сам необработанный указатель. Таким образом, std::vector<std::unique_ptr<MyObject>> должно иметь то же представление, что и std::vector<MyObject*> - таким образом, должна быть возможность передать его функции, которая ожидает Span<MyObject *const>.

Мой вопрос:

  1. Возможно ли такое приведение с текущим предложением std::span, не вызывая неопределенного поведения и не полагаясь на грязные хаки?

  2. Если это не так, может ли это быть ожидается в следующих стандартах (например, C ++ 23)?

  3. В чем опасность использования приведения, который я реализовал в моей версии Span, с использованием подвоха memcpy? Кажется, на практике это работает нормально, но я предполагаю, что в нем может быть какое-то неопределенное поведение. Если есть, то в каких случаях это неопределенное поведение может выстрелить мне в ногу на MSV C, G CC или Clang / LLVM, и как именно? Я был бы признателен за некоторые реальные примеры такого сценария ios, если они возможны.

Мой код выглядит так:

namespace detail
{
  constexpr std::size_t dynamic_extent = static_cast<std::size_t>(-1);

  template<typename SourceSmartPointer, typename SpanElement, typename = void>
  struct is_smart_pointer_type_compatible_impl
    : std::false_type
  {
  };

  template<typename SourceSmartPointer, typename SpanElement>
  struct is_smart_pointer_type_compatible_impl<SourceSmartPointer, SpanElement,
                                               decltype((void)(std::declval<SourceSmartPointer&>().get()))>
    : std::conjunction<
        std::is_pointer<SpanElement>,
        std::is_const<SpanElement>,
        std::is_convertible<std::add_pointer_t<decltype(std::declval<SourceSmartPointer&>().get())>,
                            SpanElement*>,
        std::is_same<std::remove_cv_t<std::remove_pointer_t<decltype(std::declval<SourceSmartPointer&>().get())>>,
                     std::remove_cv_t<std::remove_pointer_t<SpanElement>>>,
        std::bool_constant<(sizeof(SourceSmartPointer) == sizeof(SpanElement)) &&
                           (alignof(SourceSmartPointer) == alignof(SpanElement))>>
  {
  };

  // Helper type trait which detects whether a contiguous range of smart pointers of the source type
  // can be used to initialize a span of respective immutable raw pointers using a memcpy-based hack.
  template<typename SourceSmartPointer, typename SpanElement>
  struct is_smart_pointer_type_compatible
    : is_smart_pointer_type_compatible_impl<SourceSmartPointer, SpanElement>
  {
  };

  template<typename T, typename R>
  inline T* cast_smart_pointer_range_data_to_raw_pointer(R& source_range)
  {
    T* result = nullptr;

    auto* source_range_data = std::data(source_range);
    std::memcpy(&result, &source_range_data, sizeof(T*));

    return result;
  }
}

template<typename T, std::size_t Extent = detail::dynamic_extent>
class Span final
{
public:
  // ...

  // Non-standard extension.
  // Allows, e.g., to convert `std::vector<std::unique_ptr<Object>>` to `Span<Object *const>`
  // by using the fact that such smart pointers are bytewise equal to the resulting raw pointers;
  // `const` is required on the destination type to ensure that the source smart pointers
  // will be read-only for the users of the resulting Span.
  template<typename R,
           std::enable_if_t<std::conjunction<
             std::bool_constant<(Extent == detail::dynamic_extent)>,
             detail::is_smart_pointer_type_compatible<std::remove_reference_t<decltype(*std::data(std::declval<R&&>()))>, T>,
             detail::is_not_span<R>,
             detail::is_not_std_array<R>,
             std::negation<std::is_array<std::remove_cv_t<std::remove_reference_t<R>>>> >::value, int> = 0>
  constexpr Span(R&& source_range)
    : _data(detail::cast_smart_pointer_range_data_to_raw_pointer<T>(source_range))
    , _size(std::size(source_range))
  {
  }

  // ...

private:
  T* _data = nullptr;
  std::size_t _size = 0;
};

1 Ответ

1 голос
/ 03 февраля 2020

Возможно ли такое приведение с текущим предложением std :: span, не вызывая неопределенного поведения и не полагаясь на грязные хаки?

Нет. Даже если это утверждение верно (и я не знаю ни одного требования в стандарте, которое заставляет это быть верным):

a std::unique_ptr<T> с удалителем без сохранения состояния имеет тот же размер и выравнивание, что и необработанный T* указатель, и все, что он хранит внутри - это не что иное, как сам необработанный указатель.

Это не имеет значения. unique_ptr<T> - это не просто T* с некоторыми функциями-членами, прикрепленными к нему. Это unique_ptr<T>, и попытка сделать вид, что одно является другим, является UB из-за нарушения правила строгого наложения имен.

Если это не так, можно ли ожидать этого в следующих стандартах (например, C ++ 23)?

Нет. Даже если форма P0593 найдет свой путь в стандарт таким образом, что байты, хранящиеся в массиве unique_ptr<T>, будут преобразованы в массив T*, это будет преобразование , а не актерский состав. Таким образом, время жизни этих unique_ptr<T> s закончится, и время жизни массива T* s начнется с использованием данных в ранее завершенном объекте. Таким образом, вы не сможете использовать vector<unique_ptr<T>> снова после этого.

Любое такое преобразование, если оно будет разрешено, будет решительно односторонним. Способность P0593 неявно создавать объекты в байтах хранилища ограничена типами, которые по сути являются просто байтами данных, и unique_ptr не вписывается в это ограничение.

...