替换失败是否会阻止特殊成员函数的生成?
Does substitution failure block special member function generation?
我试图从非表面层面理解为什么以下代码无法编译:
#include <vector>
template<typename T>
struct wrapper {
T wrapped_value;
wrapper() {}
template<typename... Args>
wrapper(Args&&... args) : wrapped_value( std::forward<Args>(args)... ) {
}
};
struct A {
int a;
A(int i = 0) : a(i) {
}
};
int main() {
std::vector<wrapper<A>> v1;
std::vector<wrapper<A>> v2;
v1 = v2;
}
我可以从 std::vector
实现中的错误消息中看出上述失败,因为 wrapper<T>
的完美转发构造函数与复制构造函数匹配。通过替换到构造函数模板中创建的复制构造函数将是
wrapper(wrapper<A>& w) : wrapped_value( w ) {
}
因为 wrapped_value
是 A 类型,这是一个错误,因为 A 没有接受 wrapper<A>
的构造函数。
但是“替换失败不是错误”不是吗?因此,当编译器尝试将构造函数模板用作复制构造函数时,构造函数模板会失败——为什么这会阻止复制构造函数的自动生成?或者不是,真正的问题与 std::vector
?
的实施有关
此外,这是一个玩具示例,但在处理这样的 类 时,在我的真实代码中解决此类问题的最佳方法是什么?
- 使用“按值传递然后移动”而不是完美转发?
- 只需将复制构造函数定义为默认值?
- 在完美转发构造函数中的可变参数之前使用 std::in_place_t 参数?
- 在通过
enable_if
等进行复制构造的情况下禁用构造函数模板。其他
替代并没有失败,特殊功能的生成也没有被阻止。模板替换导致构造函数比编译器生成的复制构造函数更匹配,因此选择它会导致语法错误。
让我们通过摆脱 std::vector
的使用来简化问题中说明的问题。以下也将无法编译:
#include <utility>
template<typename T>
struct wrapper {
T wrapped_value;
wrapper() {}
template<typename... Args>
wrapper(Args&&... args) : wrapped_value(std::forward<Args>(args)...) {
}
};
struct A {
int a;
A(int i = 0) : a(i) {
}
};
int main() {
wrapper<A> v1;
wrapper<A> v2(v1);
}
在上面的模板替换中 而不是 失败,因为应用于所需的复制构造函数。我们最终得到复制构造函数的两个重载,一个由编译器生成,作为特殊函数生成的一部分,另一个通过将 v1
的类型替换到构造函数模板中生成:
wrapper(wrapper<A>& rhs); // (1) instantiated from the perfect forwarding template
wrapper(const wrapper<A>& rhs); // (2) compiler-generated ctor.
根据 C++ 的规则 (1) 必须选择,因为原始代码中的 v1
不是 const
。您实际上可以通过将其设置为 const
来检查这一点,程序将不再无法编译。
至于如何处理这个问题,正如@jcai 在评论中提到的,Scott Meyers 在 Effective Modern C++ 中的第 27 项是关于如何处理这个问题的——基本上是归结为要么不使用完美转发,要么使用“标签分发”——所以我不会在这里深入讨论。
我试图从非表面层面理解为什么以下代码无法编译:
#include <vector>
template<typename T>
struct wrapper {
T wrapped_value;
wrapper() {}
template<typename... Args>
wrapper(Args&&... args) : wrapped_value( std::forward<Args>(args)... ) {
}
};
struct A {
int a;
A(int i = 0) : a(i) {
}
};
int main() {
std::vector<wrapper<A>> v1;
std::vector<wrapper<A>> v2;
v1 = v2;
}
我可以从 std::vector
实现中的错误消息中看出上述失败,因为 wrapper<T>
的完美转发构造函数与复制构造函数匹配。通过替换到构造函数模板中创建的复制构造函数将是
wrapper(wrapper<A>& w) : wrapped_value( w ) {
}
因为 wrapped_value
是 A 类型,这是一个错误,因为 A 没有接受 wrapper<A>
的构造函数。
但是“替换失败不是错误”不是吗?因此,当编译器尝试将构造函数模板用作复制构造函数时,构造函数模板会失败——为什么这会阻止复制构造函数的自动生成?或者不是,真正的问题与 std::vector
?
此外,这是一个玩具示例,但在处理这样的 类 时,在我的真实代码中解决此类问题的最佳方法是什么?
- 使用“按值传递然后移动”而不是完美转发?
- 只需将复制构造函数定义为默认值?
- 在完美转发构造函数中的可变参数之前使用 std::in_place_t 参数?
- 在通过
enable_if
等进行复制构造的情况下禁用构造函数模板。其他
替代并没有失败,特殊功能的生成也没有被阻止。模板替换导致构造函数比编译器生成的复制构造函数更匹配,因此选择它会导致语法错误。
让我们通过摆脱 std::vector
的使用来简化问题中说明的问题。以下也将无法编译:
#include <utility>
template<typename T>
struct wrapper {
T wrapped_value;
wrapper() {}
template<typename... Args>
wrapper(Args&&... args) : wrapped_value(std::forward<Args>(args)...) {
}
};
struct A {
int a;
A(int i = 0) : a(i) {
}
};
int main() {
wrapper<A> v1;
wrapper<A> v2(v1);
}
在上面的模板替换中 而不是 失败,因为应用于所需的复制构造函数。我们最终得到复制构造函数的两个重载,一个由编译器生成,作为特殊函数生成的一部分,另一个通过将 v1
的类型替换到构造函数模板中生成:
wrapper(wrapper<A>& rhs); // (1) instantiated from the perfect forwarding template
wrapper(const wrapper<A>& rhs); // (2) compiler-generated ctor.
根据 C++ 的规则 (1) 必须选择,因为原始代码中的 v1
不是 const
。您实际上可以通过将其设置为 const
来检查这一点,程序将不再无法编译。
至于如何处理这个问题,正如@jcai 在评论中提到的,Scott Meyers 在 Effective Modern C++ 中的第 27 项是关于如何处理这个问题的——基本上是归结为要么不使用完美转发,要么使用“标签分发”——所以我不会在这里深入讨论。