4
\$\begingroup\$

Inspired by my earlier question C++17 pointer_traits implementation, I re-implemented allocator_traits under the name my_std::allocator_traits, put in a separate header allocator_traits.hpp because <memory> is way too comprehensive:

// C++17 allocator_traits implementation

#ifndef INC_ALLOCATOR_TRAITS_HPP_D6XSISB6AD
#define INC_ALLOCATOR_TRAITS_HPP_D6XSISB6AD

#include <limits>      // for std::numeric_limits
#include <memory>      // for std::pointer_traits
#include <type_traits> // for std::false_type, etc.
#include <utility>     // for std::forward

namespace my_std {

  template <class Alloc>
  struct allocator_traits;

  namespace at_detail {

    template <class Tmpl, class U>
    struct rebind_first_param { };
    template <template <class, class...> class Tmpl, class T, class... Args, class U>
    struct rebind_first_param<Tmpl<T, Args...>, U> {
      using type = Tmpl<U, Args...>;
    };
    template <class Tmpl, class U>
    using rebind_first_param_t = typename rebind_first_param<Tmpl, U>::type;

    template <class Alloc, class T>
    auto pointer(int) -> typename Alloc::pointer;
    template <class Alloc, class T>
    auto pointer(long) -> T*;

    template <class Alloc, class T, class Ptr>
    auto const_pointer(int) -> typename Alloc::const_pointer;
    template <class Alloc, class T, class Ptr>
    auto const_pointer(long) -> typename std::pointer_traits<Ptr>::template rebind<const T>;

    template <class Alloc, class Ptr>
    auto void_pointer(int) -> typename Alloc::void_pointer;
    template <class Alloc, class Ptr>
    auto void_pointer(long) -> typename std::pointer_traits<Ptr>::template rebind<void>;

    template <class Alloc, class Ptr>
    auto const_void_pointer(int) -> typename Alloc::const_void_pointer;
    template <class Alloc, class Ptr>
    auto const_void_pointer(long) -> typename std::pointer_traits<Ptr>::template rebind<const void>;

    template <class Alloc, class Ptr>
    auto difference_type(int) -> typename Alloc::difference_type;
    template <class Alloc, class Ptr>
    auto difference_type(long) -> typename std::pointer_traits<Ptr>::difference_type;

    template <class Alloc, class Diff>
    auto size_type(int) -> typename Alloc::size_type;
    template <class Alloc, class Diff>
    auto size_type(long) -> std::make_unsigned_t<Diff>;

    template <class Alloc>
    auto pocca(int) -> typename Alloc::propagate_on_container_copy_assignment;
    template <class Alloc>
    auto pocca(long) -> std::false_type;

    template <class Alloc>
    auto pocma(int) -> typename Alloc::propagate_on_container_move_assignment;
    template <class Alloc>
    auto pocma(long) -> std::false_type;

    template <class Alloc>
    auto pocw(int) -> typename Alloc::propagate_on_container_swap;
    template <class Alloc>
    auto pocw(long) -> std::false_type;

    template <class Alloc>
    auto iae(int) -> typename Alloc::is_always_equal;
    template <class Alloc>
    auto iae(long) -> std::is_empty<Alloc>::type;

    template <class Alloc, class T>
    auto rebind_alloc(int) -> typename Alloc::rebind<T>::other;
    template <class Alloc, class T>
    auto rebind_alloc(long) -> rebind_first_param_t<Alloc, T>;

  }

  template <class Alloc>
  struct allocator_traits {
    using allocator_type = Alloc;
    using value_type = typename Alloc::value_type;

    using pointer = decltype(at_detail::pointer<Alloc, value_type>(0));
    using const_pointer = decltype(at_detail::const_pointer<Alloc, value_type, pointer>(0));
    using void_pointer = decltype(at_detail::void_pointer<Alloc, pointer>(0));
    using const_void_pointer = decltype(at_detail::const_void_pointer<Alloc, pointer>(0));

    using difference_type = decltype(at_detail::difference_type<Alloc, pointer>(0));
    using size_type = decltype(at_detail::size_type<Alloc, difference_type>(0));

    using propagate_on_container_copy_assignment = decltype(at_detail::pocca<Alloc>(0));
    using propagate_on_container_move_assignment = decltype(at_detail::pocma<Alloc>(0));
    using propagate_on_container_swap = decltype(at_detail::pocw<Alloc>(0));
    using is_always_equal = decltype(at_detail::iae<Alloc>(0));

    template <class T>
    using rebind_alloc = decltype(at_detail::rebind_alloc<Alloc, T>(0));

    static pointer allocate(Alloc& a, size_type n)
    {
      return a.allocate(n);
    }
    static pointer allocate(Alloc& a, size_type n, const_void_pointer hint)
    {
      return allocate_(a, n, hint, 0);
    }

    static void deallocate(Alloc& a, pointer p, size_type n)
    {
      a.deallocate(p, n);
    }

    template <class T, class... Args>
    static void construct(Alloc& a, T* p, Args&&... args)
    {
      construct_(a, p, 0, std::forward<Args>(args)...);
    }

    template <class T>
    static void destroy(Alloc& a, T* p)
    {
      destroy_(a, p, 0);
    }

    static size_type max_size(const Alloc& a) noexcept
    {
      return max_size_(a, 0);
    }

    static Alloc select_on_container_copy_construction(const Alloc& rhs)
    {
      return soccc(rhs, 0);
    }

  private:
    static auto allocate_(Alloc& a, size_type n, const_void_pointer hint, int)
      -> decltype(a.allocate(n, hint), void(), std::declval<pointer>())
    {
      return a.allocate(n, hint);
    }
    static auto allocate_(Alloc& a, size_type n, const_void_pointer, long)
      -> pointer
    {
      return a.allocate(n);
    }

    template <class T, class... Args>
    static auto construct_(Alloc& a, T* p, int, Args&&... args)
      -> decltype(a.construct(p, std::forward<Args>(args)...), void())
    {
      a.construct(p, std::forward<Args>(args)...);
    }
    template <class T, class... Args>
    static void construct_(Alloc&, T* p, long, Args&&... args)
    {
      ::new(static_cast<void*>(p)) T(std::forward<Args>(args)...);
    }

    template <class T>
    static auto destroy_(Alloc& a, T* p, int)
      -> decltype(a.destroy(p), void())
    {
      a.destroy(p);
    }
    template <class T>
    static void destroy_(Alloc&, T* p, long)
    {
      p->~T();
    }

    static auto max_size_(const Alloc& a, int) noexcept
      -> decltype(a.max_size(), std::declval<size_type>())
    {
      return a.max_size();
    }
    static auto max_size_(const Alloc&, long) noexcept
      -> size_type
    {
      return std::numeric_limits<size_type>::max() / sizeof(value_type);
    }

    static auto soccc(const Alloc& rhs, int)
      -> decltype(rhs.select_on_container_copy_construction(), std::declval<Alloc>())
    {
      return rhs.select_on_container_copy_construction();
    }
    static auto soccc(const Alloc& rhs, long)
      -> Alloc
    {
      return rhs;
    }
  };

}

#endif

I used N4659 as a reference.

\$\endgroup\$
2
\$\begingroup\$

Well, it looks pretty clean, nice and right.

  1. Of course, if it really was part of the implementation, it would have to use solely reserved identifiers to avoid interacting with weird and ill-advised user-defined macros, making it look much less nice.

  2. Tmpl is a curious name for the primary type template-parameter. Please stay with the customary T, unless you have a much more telling name like Alloc.

  3. Tmpl is also a curious name for a template template parameter. TT is customary and more concise.

  4. Consider leaving names out if you don't need one, and they do not pull their weight conveying useful extra-information to the reader.

  5. I wonder what kind of logic you used to decide whether to put something as a private member, or in a private namespace for implementation-details. While there are good reasons for either, better use only one.

  6. A real implementation would probably mark ODR-used internal functions as always_inline in some implementation-defined way.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.