gpt4 book ai didi

c++ - 为什么 C++ 不尝试使用第二个模板重载?

转载 作者:行者123 更新时间:2023-12-01 14:36:32 26 4
gpt4 key购买 nike

我正在实现一个简单的智能指针类,我决定制作我自己的 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));
}

第一个重载工作得很好,至少在这个例子中是这样:

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);

但是,似乎没有使用数组的重载。如果我尝试使用数组类型 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]);

这是什么原因?这两个重载看起来实际上与 LLVM 的 libc++ 和 GNU 的 libstdc++ 中的 std::make_unique 的实现相同。它也在多个编译器上执行此操作(使用 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]; }
};

最佳答案

“转发引用”推导的模板类型参数是贪婪的,这会干扰您的重载决议。

当你打电话时:

auto box = make_box<Point3D[]>(20);

这实际上是调用make_box<T,Args...>T = Point32[]Args = int -- 这被明确解析为比调用 make_box<T[]>(std::size_t) 更好的重载.这是因为 20int 的 PR 值, 这需要转换为 std::size_t是第二次过载的准确匹配。由于重载决策总是倾向于选择不需要转换的重载,因此它会选择第一个重载。


解决此问题的方法是使用 SFINAE 来防止 Args...T 时被选中时过载是数组类型。这是为 std::make_unique 所做的为了在 T[] 之间进行选择和 T类型。 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;

SFINAE 还有其他方法可以防止这种情况发生;我只是以这种方法为例,因为它也可以防止 T[N]从被指定。

关于c++ - 为什么 C++ 不尝试使用第二个模板重载?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/63472896/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com