eva*_*cox 2 c++ templates c++11 c++17
我正在实现一个简单的智能指针类,我决定在完成后制作我自己的std::make_unique/std::make_shared函数版本以配合它。我创建了这两个重载:
// note: Box<T> is my "unique pointer" type, it has a partial specialization for T[],
// and it works as expected when created outside of these functions
template <class T, class... Args> Box<T> make_box(Args &&... args) {
auto ptr = new T(std::forward<Args>(args)...);
return Box<T>(std::move(ptr));
}
template <class T> Box<T> make_box(std::size_t size) {
auto ptr = new std::remove_extent_t<T>[size];
return Box<T>(std::move(ptr));
}
Run Code Online (Sandbox Code Playgroud)
第一个重载工作得很好,至少在这个例子中:
struct Point3D {
double x, y, z;
Point3D() = default;
Point3D(double x, double y, double z) : x{x}, y{y}, z{z} {};
};
// works exactly as expected, Box is created and does what it's supposed to
auto box = make_box<Point3D>(1.0, 2.0, 3.0);
Run Code Online (Sandbox Code Playgroud)
但是,似乎没有使用数组的重载。如果我尝试使用 T 的数组类型调用它,则程序将无法编译。下面的代码在尝试使用第一个重载时给了我一个错误,甚至没有尝试使用第二个:
// Gives an error about "allocation of incomplete type 'Point3D []'
// from inside a template instantiation of 'make_box<Point3D [], int>'.
// the overload with one template parameter isn't used
auto box = make_box<Point3D[]>(20);
// Note that this works fine, and uses the Box specialization that calls delete[]:
Box<Point3D[]> boxed(new Point3D[20]);
Run Code Online (Sandbox Code Playgroud)
这是什么原因?这两个重载似乎与std::make_uniqueLLVMlibc++和 GNU 的libstdc++. 它也在多个编译器上执行此操作(使用 GCC 10.1 和 Clang 10.0.1 进行测试,均使用-std=c++17 -Wall -Wextra -pedantic)。
编辑: Box 类的定义:
template <class T> class Box {
T *m_ptr;
public:
explicit Box(T *&&ptr) : m_ptr{ptr} {}
Box() = delete;
Box(const Box &) = delete;
Box(Box &&other) : m_ptr{other.m_ptr} {}
~Box() { delete m_ptr; }
T &operator*() const { return *m_ptr; }
T *operator->() const { return m_ptr; }
};
template <class T> class Box<T[]> {
T *m_ptr;
public:
explicit Box(T *&&ptr) : m_ptr{ptr} {}
Box() = delete;
Box(const Box &) = delete;
Box(Box &&other) : m_ptr{other.m_ptr} {}
~Box() { delete[] m_ptr; }
T &operator*() const { return *m_ptr; }
T *operator->() const { return m_ptr; }
T &operator[](std::size_t idx) { return m_ptr[idx]; }
};
Run Code Online (Sandbox Code Playgroud)
“转发引用”推导出的模板类型参数是贪婪的,这会干扰您的重载决议。
你打电话时:
auto box = make_box<Point3D[]>(20);
Run Code Online (Sandbox Code Playgroud)
这实际上是make_box<T,Args...>用T = Point32[]and调用Args = int——这被明确地解析为比调用更好的重载make_box<T[]>(std::size_t)。这是因为它20是 a 的 PR 值int,需要进行转换才能std::size_t准确匹配第二个重载。由于重载决议总是倾向于选择不需要转换的重载,因此它选择第一个重载。
对此的解决方法是使用 SFINAE 来防止Args...在T是数组类型时选择重载。这样做是std::make_unique为了在 aT[]和Ttype之间进行选择。std::make_unique通常实现的方式是通过 SFINAE 检测何时T是标量、有界数组或无界数组类型,并相应地呈现重载。
使用这种方法,您的代码可以重写为:
namespace detail {
template <typename T>
struct make_box_result
{
using object = T;
};
template <typename T>
struct make_box_result<T[]>
{
using unbounded_array = T[];
};
template <typename T, std::size_t N>
struct make_box_result<T[N]>
{
using bounded_array = T[N];
};
}
// Only enable 'Args...' overload for non-array types
template <typename T, typename...Args>
Box<typename detail::make_box_result<T>::object>
make_box(Args&&...args);
// Only enable 'size_t' overload for array types (unbounded arrays).
// Prevents the greedy lookup
template <typename T>
Box<typename detail::make_box_result<T>::unbounded_array>
make_box(std::size_t size);
// Disabled for fixed types
template <typename T>
Box<typename detail::make_box_result<T>::bounded_array>
make_box() = delete;
Run Code Online (Sandbox Code Playgroud)
还有其他方法可以让 SFINAE 防止这种情况发生;我只是使用这种方法作为例子,因为它也防止T[N]被指定。