I have a function that creates a new object of the underlying type of P
. Here P
is a dereferencable type like a pointer or a smart pointer.
template<typename P>
auto make_new()
For example, for pointers and smart pointers,
struct A
{
int a = 3;
};
A* a = make_new<A*>();
std::cout << a->a << std::endl;
delete a;
std::shared_ptr<A> b = make_new<std::shared_ptr<A>>();
std::cout << b->a << std::endl;
Now, for shared pointers, I would implement make_new
as the following,
template<typename P>
auto make_new()
{
using Ptype = typename P::element_type;
return P(new Ptype);
}
which doesn't work for pointers.
Now, something that works for both pointers and smart pointers,
template<typename P>
auto make_new()
{
using Ptype = typename std::remove_reference<decltype(*P())>::type;
return P(new Ptype);
}
but doesn't work for std::optional
.
Is there a canonical way of getting the underlying type of a de-referencable object?
I know that *
and ->
can be overloaded to anything and there is no guarantee that constructor works like above, or makes sense to do.
Just want to know if there is a way and am not just finding it, or just doing something dumb.
Target. Our goal is to write a using
template which takes a dereference-able type as input, and returns the element type.
template<class T>
using element_type_t = /* stuff */;
Method. We can use SFINAE to check if there's an element_type
property, and if there's not, we fall back to using std::remove_reference<decltype(*P())>()
.
// This is fine to use in an unevaluated context
template<class T>
T& reference_to();
// This one is the preferred one
template<class Container>
auto element_type(int)
-> typename Container::element_type;
// This one is the fallback if the preferred one doesn't work
template<class Container>
auto element_type(short)
-> typename std::remove_reference<decltype(*reference_to<Container>())>::type;
Once we have this function, we can write element_type_t
by just getting the return type of element_type
.
// We alias the return type
template<class T>
using element_type_t = decltype(element_type<T>(0));
Why can't we always get the element_type by dereferencing it? If you try to always get the value type using the *
operator, that could cause issues with things like the iterator for std::vector<bool>
, which returns an object that acts like a bool, but encapsulates bit manipulations. In these cases, the element type is different than the type returned by dereferencing it.
The reason your code fails with std::optional
is because std::optional
's constructor takes the value itself, rather than a pointer to the value.
In order to determine which constructor we need, we use SFINAE again to make the determination.
// Base case - use new operator
template<class Container>
auto make_new_impl(int)
-> decltype(Container{new element_type_t<Container>})
{
return Container{new element_type_t<Container>};
}
// Fallback case, where Container takes a value
template<class Container>
auto make_new_impl(long)
-> decltype(Container{element_type_t<Container>()})
{
return Container{element_type_t<Container>()};
}
Now, we can write make_new
so that it calls make_new_impl
:
template<class Container>
auto make_new() {
return make_new_impl<Container>(0);
}
Example. We can now use make_new
to make either std::optional
, std::shared_ptr
, or even a regular pointer.
#include <optional>
#include <memory>
int main() {
// This works
int* ptr = make_new<int*>();
// This works too
std::shared_ptr<int> s = make_new<std::shared_ptr<int>>();
// This also works
std::optional<int> o = make_new<std::optional<int>>();
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With