[libcxx-commits] [PATCH] D132149: [String] Allow fancy pointer as pointer type of basic_string allocator

Brendan Emery via Phabricator via libcxx-commits libcxx-commits at lists.llvm.org
Mon Sep 19 09:06:16 PDT 2022


bemeryesr marked 4 inline comments as done.
bemeryesr added a comment.

Thanks for the feedback! Regarding your OffsetPtr example:

1. My initial code was broken as it was calling the copy assignment operator of `__long` even if `__rep` was a short string. For an offset pointer, the copy assignment operator recalculates the offset to the pointed-to address and so the copied value will be different to the original. Hence, it failed for short strings. So now I perform the copy depending on whether it's a short or a long string.
2. As I think you alluded to, it seems that some of the pointer arithmetic is optimized-away by the compiler, leading to incorrect behaviour. In boost::interprocess::offset_ptr they mentioned this: "Note: using the address of a local variable to point to another address is not standard conforming and this can be optimized-away by the compiler. Non-inlining is a method to remain illegal but correct." To deal with this, we used a couple of tricks that prevent the functions from being inlined and the test passes. Would it make sense to add this code as a unit test as is? Or perhaps we should find a better way to deal with the UB? This is the code that works for me:



  // main.cpp
  //===----------------------------------------------------------------------===//
  //
  // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
  // See https://llvm.org/LICENSE.txt for license information.
  // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
  //
  //===----------------------------------------------------------------------===//
  
  #include <cassert>
  #include <string>
  
  #include "OffsetPtr.h"
  
  int main()
  {
      using string = std::basic_string<char, std::char_traits<char>, OffsetPtrAllocator<char>>;
      {
          string str3 = "almost long string";
          string str4 = std::move(str3);
          assert(str4 == "almost long string");
      }
  }



  // OffsetPtr.h
  #ifndef OFFSETPTR_H
  #define OFFSETPTR_H
  
  #include <cassert>
  #include <cstdint>
  #include <iostream>
  #include <iterator>
  #include <type_traits>
  #include <utility>
  
  namespace detail
  {
  using local_difference_type = std::ptrdiff_t;
  
  local_difference_type SubtractPointersBytesImpl(const volatile void* ptr1, const volatile void* ptr2);
  
  // Alias for checking if template types are of the same type after stripping const/volatile qualifiers.
  template <typename T1, typename T2>
  using cv_stripped_types_equal =
      typename std::is_same<typename std::remove_cv<T1>::type, typename std::remove_cv<T2>::type>;
  
  template <typename T1, typename T2, typename = std::enable_if_t<cv_stripped_types_equal<T1, T2>::value>>
  constexpr local_difference_type SubtractPointersBytes(T1* const ptr1, T2* const ptr2)
  {
      return SubtractPointersBytesImpl(ptr1, ptr2);
  }
  
  template <typename T>
  constexpr const T* __attribute__((noinline)) AddOffsetToPointer(const T* const ptr, local_difference_type offset)
  {
      return reinterpret_cast<const T*>(reinterpret_cast<const std::uint8_t*>(ptr) + offset);
  }
  
  template <typename T>
  constexpr const T* __attribute__((noinline)) AddOffsetToPointer(T* const ptr, local_difference_type offset)
  {
      return reinterpret_cast<T*>(reinterpret_cast<std::uint8_t*>(ptr) + offset);
  }
  
  }  // namespace detail
  
  template <class T>
  class alignas(std::max(alignof(T), alignof(std::ptrdiff_t))) OffsetPtr
  {
      std::ptrdiff_t offset;
  
    public:
      using value_type = T;
      using iterator_category = std::random_access_iterator_tag;
      using difference_type = detail::local_difference_type;
      using pointer = OffsetPtr;
      using reference = T&;
  
      constexpr OffsetPtr() = default;
      constexpr OffsetPtr(const OffsetPtr& other)
          : offset(detail::SubtractPointersBytes(other.operator->(), reinterpret_cast<T*>(this)))
      {
      }
  
      constexpr operator OffsetPtr<const T>() const
      {
          return OffsetPtr<const T>{detail::AddOffsetToPointer(reinterpret_cast<const T*>(this), offset)};
      }
  
      constexpr OffsetPtr& operator=(const OffsetPtr& other)
      {
          T* ptr = other.operator->();
          offset = detail::SubtractPointersBytes(ptr, reinterpret_cast<T*>(this));
          return *this;
      }
  
      constexpr explicit OffsetPtr(T* ptr) : offset(detail::SubtractPointersBytes(ptr, reinterpret_cast<T*>(this))) {}
  
      constexpr T* operator->() const
      {
          return const_cast<T*>(detail::AddOffsetToPointer(reinterpret_cast<T*>(const_cast<OffsetPtr*>(this)), offset));
      }
  
      constexpr T& operator[](size_t i) { return operator->()[i]; }
  
      static OffsetPtr pointer_to(T& e) { return OffsetPtr(&e); }
  
      friend OffsetPtr operator+(const OffsetPtr& lhs, size_t rhs)
      {
          return OffsetPtr{detail::AddOffsetToPointer(lhs.operator->(), rhs)};
      }
  };
  
  static_assert(sizeof(OffsetPtr<int>) == 8, "");
  
  template <class T>
  class OffsetPtrAllocator
  {
    public:
      using value_type = T;
      using pointer = OffsetPtr<T>;
  
      pointer allocate(size_t n) { return pointer(std::allocator<T>{}.allocate(n)); }
  
      void deallocate(pointer ptr, size_t n) { std::allocator<T>{}.deallocate(ptr.operator->(), n); }
  };
  
  #endif  // OFFSETPTR_H

  // OffsetPtr.cpp
  #include "OffsetPtr.h"
  
  namespace detail
  {
  
  local_difference_type SubtractPointersBytesImpl(const volatile void* const ptr1, const volatile void* const ptr2)
  {
      return local_difference_type(reinterpret_cast<local_difference_type>(ptr1) -
                                   reinterpret_cast<local_difference_type>(ptr2));
  }
  
  }  // namespace detail



================
Comment at: libcxx/include/string:691
                   "Allocator::value_type must be same type as value_type");
+    static_assert((is_trivially_destructible<pointer>::value), "pointer type must have a trivial destructor.");
 
----------------
philnik wrote:
> AFAICT pointers don't have to have trivial destructors.
Yeah, you're right. As of C++20, we can use constexpr destructors. Before C++20 they must be trivial in order for basic_string to be constexpr.


Repository:
  rG LLVM Github Monorepo

CHANGES SINCE LAST ACTION
  https://reviews.llvm.org/D132149/new/

https://reviews.llvm.org/D132149



More information about the libcxx-commits mailing list