我正在寻找创建一个包装指针
SpecialPtr
,它遵循 5 规则,但下面还有一个模板化构造函数。当在向量中使用包装指针时,向量类想要使用模板化构造函数,从而导致像这样的编译错误
<source>:22:9: error: no matching function for call to 'Foo::Foo(SpecialPtr<Foo>&)'
22 | new T(std::forward<F>(first), std::forward<Args...>(args)...);
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
<source>:7:14: note: candidate: 'Foo::Foo(int)'
7 | explicit Foo(int x): val(x) {}
| ^~~
<source>:7:22: note: no known conversion for argument 1 from 'SpecialPtr<Foo>' to 'int'
7 | explicit Foo(int x): val(x) {}
完整链接https://godbolt.org/z/nMjExMT1a
这让我感到困惑,因为我希望非模板化构造函数优先于它。下面的代码之所以有效,是因为添加了
requires
,为什么有必要?
#include <vector>
#include <memory>
using namespace std;
struct Foo {
explicit Foo(int x): val(x) {}
int val;
};
template<typename T>
struct SpecialPtr {
SpecialPtr() noexcept = default;
SpecialPtr(const SpecialPtr&) = default;
SpecialPtr(SpecialPtr&&) = default;
~SpecialPtr() = default;
SpecialPtr& operator=(const SpecialPtr&) = default;
SpecialPtr& operator=(SpecialPtr&&) = default;
template<typename F, typename... Args>
// Why is this requires necessary?
requires(!std::derived_from<std::decay_t<F>, SpecialPtr>)
explicit SpecialPtr(F&& first, Args&&... args) {
new T(std::forward<F>(first), std::forward<Args...>(args)...);
}
T* ptr;
};
int main () {
vector<SpecialPtr<Foo>> v;
vector<SpecialPtr<Foo>> v_2;
v_2 = v; // Entrypoint for the error
return 0;
}
代码可以简化
SpecialPtr<Foo> v;
SpecialPtr<Foo> v_2(v);
当编译器从
v_2
复制构造 v
时,它会尝试将构造函数与参数类型 SpecialPtr<Foo>&
相匹配。构造函数模板是最佳匹配,因为它不需要任何参数转换。因此,编译器选择完全匹配的模板,并且无法使用其参数。
复制构造函数
SpecialPtr(const SpecialPtr&)
匹配得不太好,因为它需要参数转换SpecialPtr<Foo>&
→const SpecialPtr<Foo>&
。
您可以添加重载的复制构造函数
SpecialPtr(SpecialPtr&) = default;
它将与参数完全匹配,并且将在模板之前选择自由函数。