我有一个函数可以创建基础类型为P
的新对象。这里的P
是可取消引用的类型,如指针或智能指针。
template<typename P>
auto make_new()
例如,对于指针和智能指针,
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;
现在,对于共享指针,我将实现make_new
,如下所示,
template<typename P>
auto make_new()
{
using Ptype = typename P::element_type;
return P(new Ptype);
}
不适用于指针。
现在,既适用于指针又适用于智能指针的东西
template<typename P>
auto make_new()
{
using Ptype = typename std::remove_reference<decltype(*P())>::type;
return P(new Ptype);
}
但不适用于std::optional
。
是否存在获取可取消引用对象的基础类型的规范方法?
我知道*
和->
可以重载到任何东西,并且不能保证构造函数可以像上面那样工作或有意义。
只想知道是否有办法,而不仅仅是找到它,或者只是做一些愚蠢的事情。
答案 0 :(得分:5)
目标。我们的目标是编写一个using
模板,该模板采用可取消引用的类型作为输入,并返回元素类型。
template<class T>
using element_type_t = /* stuff */;
方法。。我们可以使用SFINAE来检查是否存在element_type
属性,如果没有,则退回到使用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;
一旦有了此功能,我们就可以通过获取返回类型element_type_t
来编写element_type
。
// We alias the return type
template<class T>
using element_type_t = decltype(element_type<T>(0));
为什么我们不能总是通过取消引用来获取element_type?如果您尝试始终使用*
运算符获取值类型,则可能导致诸如迭代器之类的问题用于std::vector<bool>
,它返回一个行为类似于bool,但封装了位操作的对象。在这些情况下,元素类型与通过取消引用返回的类型不同。
您的代码因std::optional
而失败的原因是因为std::optional
的构造函数本身使用值,而不是指向该值的指针。
为了确定我们需要哪个构造函数,我们再次使用SFINAE进行确定。
// 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>()};
}
现在,我们可以编写make_new
,以便它调用make_new_impl
:
template<class Container>
auto make_new() {
return make_new_impl<Container>(0);
}
示例。我们现在可以使用make_new
来制作std::optional
,std::shared_ptr
甚至是常规指针。
#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>>();
}