11
\$\begingroup\$

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.

\$\endgroup\$
3
  • 1
    \$\begingroup\$ Do you have a test program? Or at least a motivating example of where this is useful? \$\endgroup\$ Nov 18, 2021 at 8:44
  • \$\begingroup\$ I'd like to know what the rebind member is for - that's one thing that's not also in iterator_traits<T*>. \$\endgroup\$ Nov 18, 2021 at 9:05
  • 1
    \$\begingroup\$ @TobySpeight, from en.cppreference.com/w/cpp/memory/pointer_traits : 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\$
    – alfC
    Feb 5 at 22:08

1 Answer 1

4
\$\begingroup\$

I tried a little bit your code and seems to work.

If you want my opinion (review?) I find the techniques quite arcane. Perhaps it is my C++17 perspective, or maybe that the code you posted needs to be compatible with C++98 or 11 (?). I will base my review on the C++17 perspective since that is one of the labels of your post.

First, I would factor out the manipulation of the first argument in a single class:

template<class> struct first_param {};

template<template<class, class...> class Tmpl, class T, class... Args>
struct first_param<Tmpl<T, Args...>> {
    using type = T;
    template<class U> using rebind = Tmpl<U, Args...>;
};

Second, I would replace the trick of int, long, matching plus decltype with a modern tool designed to detect valid operations on types, which is std::experimental::detect_or_t.

The class basically will tell if an operation is valid on a type and if not a fallback type is used.

These are the operations that hypothetically need to be applied to the type,

template<class T> using element_type_t = typename T::element_type;
template<class T> using difference_type_t = typename T::difference_type;

The tricky one is the one that rebinds:

template<class U>
struct bind_second {
    template<class T> using rebind_t = typename T::rebind<U>;
};

Note that at the end it is simply adding a level of indirection.

As powerful as detect_or_t is, I profoundly dislike its syntax because it is impossible to figure out the meaning from reading: the first argument is the fallback, the second argument is the operator and the third argument is the target (probed) type.

For this reason, I rearrange the dectect_or_t<fallback, operation, target> into a metafunction that is more readable valid_on<operation, target>::otherwise<fallback>:

    template<template<class...> class Op, class Target>
    struct valid_on {
        template<class Fallback> using otherwise = std::experimental::detected_or_t<Fallback, Op, Target>;
    };

This way each fallback case is read as in the documentation, that is in plain English:

template <class Ptr>
struct pointer_traits {
    using pointer = Ptr;

    using element_type    = typename valid_on<element_type_t   , pointer>::otherwise<typename first_param<Ptr>::type>;
    using difference_type = typename valid_on<difference_type_t, pointer>::otherwise<std::ptrdiff_t                 >;
    template<class U>
    using rebind          = typename valid_on<bind_second<U>::template rebind_t, pointer>::otherwise<typename first_param<Ptr>::rebind<U>>;

    static constexpr pointer pointer_to(std::add_lvalue_reference<element_type> r) {
        return Ptr::pointer_to(r);
    }
};

Finally, I encapsulate the needed functionality in a base class and add the T* specialization. The final full code looks like this:

template<class> struct first_param {};

template<template<class, class...> class Tmpl, class T, class... Args>
struct first_param<Tmpl<T, Args...>> {
    using type = T;
    template<class U> using rebind = Tmpl<U, Args...>;
};

class pointer_trait_detail {
 protected:
    template<class T> using element_type_t = typename T::element_type;
    template<class T> using difference_type_t = typename T::difference_type;
    template<class U>
    struct bind_second {
        template<class T> using rebind_t = typename T::rebind<U>;
    };
    template<template<class...> class Op, class Target>
    struct valid_on {
        template<class Fallback> using otherwise = std::experimental::detected_or_t<Fallback, Op, Target>;
    };
};

template <class Ptr>
struct pointer_traits : private pointer_trait_detail {
    using pointer = Ptr;

    using element_type    = typename valid_on<element_type_t   , pointer>::otherwise<typename first_param<Ptr>::type>;
    using difference_type = typename valid_on<difference_type_t, pointer>::otherwise<std::ptrdiff_t                 >;
    template<class U>
    using rebind          = typename valid_on<bind_second<U>::template rebind_t, pointer>::otherwise<typename first_param<Ptr>::rebind<U>>;

    static constexpr 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);
    }
};

Here is the full working code (in a namespace stx), alongside your code (in namespace my_std) and some test cases: https://godbolt.org/z/zGMhEssoc

It works with GCC and clang (C++17 mode). Unfortunately clang requires inserting a few ::template keywords spinkled that make the code a bit less readable (as seen in the Godbolt link).

If you find an error in my code I appreciate corrections.

\$\endgroup\$
7
  • \$\begingroup\$ Thanks for the review! I agree that the detection idiom would make the code more straightforward than using the "arcane techniques" of overload resolution, and I like the syntax valid_on<Op, T>::otherwise<Fallback> that you proposed. I guess std::conditional_t<requires { typename T::member_type; }>, typename T::member_type, Fallback> would also be an option now that we have concepts (still, it would be a good idea to wrap it and eliminate the repetition of typename T::member_type). \$\endgroup\$
    – L. F.
    Feb 5 at 22:45
  • \$\begingroup\$ The part about the template keyword is new to me — I was actually under the impression that this is ill-formed, but it seems not :/ I will come back to cast my accept after a while. \$\endgroup\$
    – L. F.
    Feb 5 at 22:46
  • \$\begingroup\$ Unfortunatelly, the std::conditional_t<requires ..., typename T::member_type, ...> would not work because it will give a hard error on the second argument when it doesn't exists. \$\endgroup\$
    – alfC
    Feb 6 at 0:34
  • \$\begingroup\$ I can't give an explanation for template and also find gcc and clang to be inconsistent about its need. (the godbolt link has all the template necessary for clang). I tend to intuit them lately when things do not work. \$\endgroup\$
    – alfC
    Feb 6 at 0:36
  • 1
    \$\begingroup\$ I was under the impression that this usage of template must be followed by a template argument list (i.e., X::template Y<Z>), and I was not aware that it is valid even without the <Z> part. \$\endgroup\$
    – L. F.
    Feb 8 at 12:20

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

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