1

When writing generic code, I often find myself in the situation in which the datatype T provided by the user of my lib might need to be directly a concrete type of some sort, but at the same time also a pointed type. This represents a problem when the generic operations in the lib are meant for the pointed type, and not for the pointer. Is there a standard way to dereference it? For example, something like this:

namespace std
{
template <typename T>
T& deref( T& t ){ return t; }

template <typename T>
T& deref( T* t ){ return *t; }

template <typename T>
T& deref( std::unique_ptr<T>& t ){ return *t; }

template <typename T>
T& deref( std::shared_ptr<T>& t ){ return *t; }

template <typename T>
T& deref( std::reference_wrapper<T>& t ){ return t.get(); }
}

So that my code will store T in his original form decided by the user, but will perform operations on std::deref( t ) (e.g. operator== or operator<<)

nyarlathotep108
  • 5,275
  • 2
  • 26
  • 64
  • You're not allowed to put things in the `std` namespace. Adding your own functions, classes, templates etc. makes your program ill-formed (IIRC, or is it even undefined behavior?) – Some programmer dude Feb 06 '20 at 10:56
  • 2
    @Someprogrammerdude I know, this is just an example. The question is if there is such a thing in STL or in the language in any form, I am not suggesting I am adding anything to `std` in my user code by any stretch. – nyarlathotep108 Feb 06 '20 at 11:00
  • 2
    Hm, I'm not aware of, so I suppose you'll have to write it yourself in the fashion that you presented above (not in namespace `std` then, of course). But instead of limiting to `std` smart pointers, I'd rather look with SFINAE if an `operator*` or `operator->` exists and then apply it, too. – Aconcagua Feb 06 '20 at 11:01
  • [Checking function existence via SFINAE](https://stackoverflow.com/questions/257288/is-it-possible-to-write-a-template-to-check-for-a-functions-existence)... – Aconcagua Feb 06 '20 at 11:03
  • @Aconcagua I would use `std::is_pointer` but it should not work with reference_wrapper afaik. I really would expect something like `std::deref` this to be in the STL in some form, is it really not there? – nyarlathotep108 Feb 06 '20 at 11:07
  • What should be the behaviour with reference wrapped pointer? – eerorika Feb 06 '20 at 11:12
  • @eerorika to get back `T` from `std::reference_wrapper`, because sometimes the implicit cast to `T` is not gonna work, specially in generic code where normally `auto` is everywhere. – nyarlathotep108 Feb 06 '20 at 11:14
  • 2
    What do you expect from `T**`? no `nullptr` check for others? – Jarod42 Feb 06 '20 at 11:23
  • @jarod42 I was not thinking about `T**` actually, but I would expect to return `T&` also in that case. For `nullptr` I'd say it's UB as much as it is UB also when you call `operator*` upon any `nullptr`, I wouldn't care – nyarlathotep108 Feb 06 '20 at 11:41
  • Can you give a real-life example for the usage of this functionality? So far, I can't really imagine a functionality that strips an undetermined number of levels of pointer (or other things). – L. F. Feb 09 '20 at 10:20

1 Answers1

1

As far as I am aware, there isn't any such functionionality. Inspired by this answer, it's is not too heavy to write on your own (well, you did yourself already, this variant will extend to anything providing an operator*, though):

// first starting with some helper functions:

// return references as references!
template <typename T>
decltype(auto) doDeref(T& t, ...)
{
    return t;
}
// if some custom operator* returns a temporary, we cannot bind to normal
// reference, so we'll accept as rvalue reference instead:
template <typename T>
decltype(auto) doDeref(T&& t, ...)
{
    return std::move(t);
}
// specifically: anything that can be dereferenced:
template <typename T>
decltype(auto) doDeref(T& t, std::remove_reference<decltype(*t)>*)
//                                ^ to avoid trying to form pointer to reference
{
    return doDeref(*t, nullptr);
}
// a temporary that might be dereferencable:
template <typename T>
decltype(auto) doDeref(T&& t, std::remove_reference<decltype(*t)>*)
{
    return doDeref(*t, nullptr);
}
// reference wrapper; accept as const (!)
// be aware that return type still is non-const unless underlying
// type (i. e. T) is already const as well!
decltype(auto) doDeref(std::reference_wrapper<T> const& t)
{
    return doDeref(t.get(), nullptr);
}

// and finally: the main functions calling the helpers:

template <typename T>
decltype(auto) deref(T& t)
{
    return doDeref(t, nullptr);
}
// again to allow temporaries...
template <typename T>
decltype(auto) deref(T&& t)
{
    return doDeref(std::move(t), nullptr);
}

Bonus: That's even extensible for further types that do not fit in to normal de-referencing pattern (like std::reference_wrapper), just provide another overload calling the appropriate getter (if the class provides both const and non-const getter, you might need to provide all of the overloads for non-const and const ordinary references as well as for rvalue reference).

Aconcagua
  • 24,880
  • 4
  • 34
  • 59