2

Say I have a type with a function f():

struct A { void f() {} };

and two vectors:

std::vector<A*>       x;
std::vector<A*******> y; 

(The silly number of pointers is just for dramatic effect.)

I'm looking for a way to be able to write:

deep_deref(std::begin(x)).f();
deep_deref(std::begin(y)).f();

In other words, what I want is a uniform dereferencing syntax powered by a universal, multi-level, smart dereferencing function (or something else that'll allow uniform dereferencing syntax) deep_deref() that will dereference the object passed to it, then the object obtained from that dereference, then the next, and so on until it arrives at a non-dereferencable object, at which point it'll return the final object.

Note that along this path of dereferencing there may lie all kinds of dereferencable objects: pointers, iterators, smart pointers, etc — anything dereferencable.

Is something like this possible? (Assume I have is_dereferencable.)

Leo Heinsaar
  • 3,887
  • 3
  • 15
  • 35
  • Can't you write a _type traits_ based recursive template using [`std::is_pointer`](http://en.cppreference.com/w/cpp/types/is_pointer) or such? – user0042 Aug 08 '17 at 16:09
  • 2
    @user0042 The `std::is_pointer` struct only checks if `T` is a *pointer*, not an iterator, smart pointer, etc. – Daniel H Aug 08 '17 at 16:10
  • Are you looking for [std::invoke](http://en.cppreference.com/w/cpp/utility/functional/invoke)? – Jesper Juhl Aug 08 '17 at 16:11
  • You need to build a `is_dereferenceable` and then recursively call a function until that returns `false`, then return the pointed to thing. – NathanOliver Aug 08 '17 at 16:11
  • @DanielH Shouldn't smart pointers and iterators behave like pointers regarding dereferencing. That's just a rough idea (maybe some more complex _pointer ike type_ constraints will be needed). – user0042 Aug 08 '17 at 16:13
  • 3
    This seems to be a valid solution https://stackoverflow.com/questions/20222059/recursively-dereference-pointer – Pruthvikar Aug 08 '17 at 16:17
  • @Pruthvikar `std::decay` looks nifty for that job, yes. – user0042 Aug 08 '17 at 16:21
  • @Pruthvikar The [accepted answer there](https://stackoverflow.com/a/20223229/4973224) is exactly what I wanted. Thanks. – Leo Heinsaar Aug 08 '17 at 16:43
  • @LeoHeinsaar, interesting question, I would say the dereferencing is the least of the problems. The main problem is how to manage the memory. – alfC Aug 08 '17 at 16:54
  • Do you have to use C++11? (just to be sure) – HolyBlackCat Aug 08 '17 at 16:56
  • 1
    @LeoHeinsaar posted as an answer to help other searchers! – Pruthvikar Aug 09 '17 at 13:26

5 Answers5

1
template < typename T, bool isDeref = is_dereferenceable<T>::value >
struct deref_
{
    static T& call(T & t) { return t; }
};

template < typename T >
struct deref_<T,true>
{
    static decltype(*declval<T>()) call(T & t) { return deref_<decltype(*t)>::call(*t); }
};

template < typename T >
auto deref(T & t) { return deref_<T>::call(t); }

This is untested and incomplete. You should be using && and forward and all that. Much cleanup and re-ordering to do...

I also question the wisdom of having something like this around.

Edward Strange
  • 40,307
  • 7
  • 73
  • 125
1

Using std::decay to create a can_dereference function by dereferencing and removing CV qualifiers, this can be done.

The answer linked to here is a full implementation along with a live sample.

I originally posted this as a comment but thought an answer would be better to help people searching

Pruthvikar
  • 430
  • 4
  • 15
0

Some boilerplate:

namespace details {
  template<template<class...>class, class, class...>
  struct can_apply:std::false_type{};
  template<class...>struct voider{using type=void;};
  template<class...Ts>using void_t=typename voider<Ts...>::type;
  template<template<class...>class Z, class... Ts>
  struct can_apply<Z, void_t<Z<Ts...>>, Ts...>:std::true_type{};

} templateclass Z, class... Ts> using can_apply = details::can_apply; A trait to determine if you should *:

template<class T>
using unary_star_r = decltype( *std::declval<T>() );

template<class T>
using can_unary_star = can_apply<unary_star_r, T>;

dispatch takes two arguments and picks between them at compile time:

template<bool /*false*/>
struct dispatch_t {
  template<class T, class F>
  F operator()(T, F f)const{ return std::move(f); }
};
template<>
struct dispatch_t<true> {
  template<class T, class F>
  T operator()(T t, F)const{ return std::move(t); }
};

#define RETURNS(...) \
  noexcept(noexcept(__VA_ARGS__))\
  ->decltype(__VA_ARGS__)\
  { return __VA_ARGS__; }
template<bool b, class T, class F>
auto
dispatch( T t, F f )
RETURNS( dispatch_t<b>{}( std::move(t), std::move(f) ) )

We are almost done...

Now our work. We write function objects that represent dereferencing a type, doing nothing, and maybe doing either:

struct maybe_deref_t;
struct do_deref_t;
struct identity_t {
  template<class T>
  T operator()(T&& t)const { return std::forward<T>(t); }
};

struct do_deref_t {
  template<class T>
  auto operator()(T&& t)const
  RETURNS( maybe_deref_t{}( *std::forward<T>(t) ) )
};

Here is the work:

struct maybe_deref_t {
  template<class T>
  auto operator()(T&& t)const
  RETURNS(
    dispatch< can_unary_star<T>::value >(
      do_deref_t{},
      identity_t{}
    )(
      std::forward<T>(t)
    )
  )
};

and a helper for better syntax:

template<class T>
auto maybe_deref( T&& t )
RETURNS( maybe_deref_t{}( std::forward<T>(t) ) )

Test code:

int main() {
    auto bob = new int*( new int(7) ); // or 0 or whatever
    std::cout << maybe_deref(bob) << "\n";
}

live example.

I originally wrote this in a C++14 style, then back-translated it to C++11. In C++14 it is much cleaner.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
0

For anybody who can use a more recent version of C++:

#include <utility>

namespace detail
{
struct Rank_0 {};
struct Rank_1 : Rank_0{}; // disambiguate overloads

template <class T, std::void_t<decltype(*std::declval<T>())>* = nullptr>
decltype(auto) deep_deref_impl(T& obj, Rank_1)
{
    return deep_deref_impl(*obj, Rank_1{});
}

template <class T>
decltype(auto) deep_deref_impl(T& obj, Rank_0)
{
    return obj;
}
}

template <class T>
decltype(auto) deep_deref(T& obj)
{
    return detail::deep_deref_impl(obj, detail::Rank_1{});
}
auto test()
{
    int a = 24;
    int* p1 = &a;
    int** p2 = &p1;
    int*** p3 = &p2;
    int**** p4 = &p3;

    deep_deref(a) += 5;
    deep_deref(p4) += 11;

    return a; // 40
}

Check it at godbolt

bolov
  • 72,283
  • 15
  • 145
  • 224
0

I like to keep things simple... I'd implement it like this:

template <class T>
auto deep_deref_impl(T&& t, int) -> decltype(deep_deref_impl(*t, int{})) {
    return deep_deref_impl(*t, int{});
}

template <class T>
T &deep_deref_impl(T&& t, ...) {
    return t;
}

template <class T>
auto deep_deref(T&& t) -> decltype(deep_deref_impl(std::forward<T>(t), int{})) {
    return deep_deref_impl(std::forward<T>(t), int{});
}

[live demo]

W.F.
  • 13,888
  • 2
  • 34
  • 81