pointer_traits
is a lightweight trait that provides a uniform interface to builtin pointers and user-defined fancy pointers. That said, things like element_type
require
template metaprogramming techniques to determine. This makes pointer_traits
a good exercise.
Here's my re-implementation under the name my_std::pointer_traits
, put in a separate header pointer_traits.hpp
because <memory>
is way too comprehensive:
// C++17 pointer_traits implementation
#ifndef INC_POINTER_TRAITS_HPP_60HzB0lbek
#define INC_POINTER_TRAITS_HPP_60HzB0lbek
#include <cstddef> // for std::ptrdiff_t
#include <memory> // for std::addressof
#include <type_traits> // for std::add_lvalue_reference
namespace my_std {
template <class Ptr>
struct pointer_traits;
namespace pt_detail {
template <class Tmpl>
struct get_first_param { };
template <template <class, class...> class Tmpl, class T, class... Args>
struct get_first_param<Tmpl<T, Args...>> {
using type = T;
};
template <class Tmpl>
using get_first_param_t = typename get_first_param<Tmpl>::type;
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 Ptr>
auto element(int) -> typename Ptr::element_type;
template <class Ptr>
auto element(long) -> get_first_param_t<Ptr>;
template <class Ptr>
auto diff(int) -> typename Ptr::difference_type;
template <class Ptr>
auto diff(long) -> std::ptrdiff_t;
template <class Ptr, class U>
auto rebind(int) -> typename Ptr::template rebind<U>;
template <class Ptr, class U>
auto rebind(long) -> rebind_first_param_t<Ptr, U>;
}
template <class Ptr>
struct pointer_traits {
using pointer = Ptr;
using element_type = decltype(pt_detail::element<Ptr>(0));
using difference_type = decltype(pt_detail::diff<Ptr>(0));
template <class U>
using rebind = decltype(pt_detail::rebind<Ptr, U>(0));
static pointer pointer_to(std::add_lvalue_reference<element_type> r)
{
return Ptr::pointer_to(r);
}
};
template <class T>
struct pointer_traits<T*> {
using pointer = T*;
using element_type = T;
using difference_type = std::ptrdiff_t;
template <class U>
using rebind = U*;
static pointer pointer_to(std::add_lvalue_reference<element_type> r)
{
return std::addressof(r);
}
};
}
#endif
I used N4659 as a reference.
rebind
member is for - that's one thing that's not also initerator_traits<T*>
. \$\endgroup\$template <class U> using rebind ---> Ptr::rebind<U> if exists, otherwise Template<U, Args...> if Ptr is a template specialization Template<T, Args...>
. In simple words, it converts a given pointer of T to the same class of pointer to U. This is a very common operation for pointers (e.g. when allocating nodes, you need to transform the pointer to the element type to the pointer of the node type). It is not so common to need this with iterators. \$\endgroup\$