替换失败是否会阻止特殊成员函数的生成?

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?

的实施有关

此外,这是一个玩具示例,但在处理这样的 类 时,在我的真实代码中解决此类问题的最佳方法是什么?

  1. 使用“按值传递然后移动”而不是完美转发?
  2. 只需将复制构造函数定义为默认值?
  3. 在完美转发构造函数中的可变参数之前使用 std::in_place_t 参数?
  4. 在通过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 项是关于如何处理这个问题的——基本上是归结为要么不使用完美转发,要么使用“标签分发”——所以我不会在这里深入讨论。