I don't know why there's no such function in std (at least I didn't find one). I need a conditional template function to return a reference to an object of T. T might be passed as a pointer or a smart pointer or by reference:
template <typename T>
void do_foo(T &t)
{
auto &&res = dereference(t).foo();
}
So I wrote this:
#include <memory>
#include <type_traits>
namespace eld::traits
{
template<typename T>
struct is_pointer_like : std::is_pointer<T>
{
using value_type = T;
};
template<typename T, typename DeleterT>
struct is_pointer_like<std::unique_ptr<T, DeleterT>> : std::true_type
{
using value_type = T;
};
template<typename T>
struct is_pointer_like<std::shared_ptr<T>> : std::true_type
{
using value_type = T;
};
namespace detail
{
template<typename T, bool /*false*/ = is_pointer_like<T>::value>
struct dereference
{
constexpr explicit dereference(T &t) //
: value(t)
{
}
T &value;
};
template<typename PtrT>
struct dereference<PtrT, true>
{
constexpr explicit dereference(PtrT &ptrT)
: value(*ptrT)
{}
typename is_pointer_like<PtrT>::value_type &value;
};
} // namespace detail
template <typename T>
constexpr auto& dereference(T &t)
{
return detail::dereference<std::decay_t<T>>(t).value;
}
template <typename T>
constexpr const auto& dereference(const T &t)
{
return detail::dereference<const std::decay_t<T>>(t).value;
}
} // namespace eld::traits
This code does not support the concept of shared pointer though. dereference does not increment a counter, so using an obtained reference is dangerous. Because when std::shared_ptr is used the user is expected to hold a copy of it (like with std::weak_ptr).
Taking it into an account I suppose using dereference as a function is a wrong approach. I think that dereference should be a persistent object if I expect it to support a concept of sharable ownership.
template <typename T>
void so_stuff(T &t)
{
dereference derefT{t};
auto &refT = derefT.value();
auto &&res = refT.foo();
}
However, this becomes convoluted in use.
Also, I feel like using is_pointer_like is redundant. Maybe I am better of just implementing dereference with SFINAE customizations for raw and smart pointers.
*somewhere in the code? \$\endgroup\$void do_stuff, I need to invoke a member function of T. But I don't want to limit T to be either a reference or a pointer. Because member invokation for the latter requires dereferencing. So template for a pointer would not compile for a reference. Hence I want an adapter that will access the member and compile in both cases. \$\endgroup\$do_stuff(*foo)when using with a pointer? (I'm assuming that's a function similar toso_stuff()in the question) Is it becausefoohas come from dereferencing an iterator or something, where the caller can't know whether it needs to dereference for itself? I'm asking because that might make a difference to the object-lifetime concerns you mentioned. \$\endgroup\$the caller can't know whether it needs to dereference for itselfexactly. In case you have a complex template logic and you assume that at some point you get an object which might be a pointer, or not. It will facilitate, for example, modification of containers. You would be able to change value type to be a pointer, without changing the logic (adding or removing dereferencing) \$\endgroup\$