【问题标题】:Does substitution failure block special member function generation?替换失败是否会阻止特殊成员函数的生成?
【发布时间】:2021-10-01 14:35:50
【问题描述】:

我试图在非肤浅的层面上理解为什么以下代码无法编译:

#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&lt;T&gt; 的完美转发构造函数与复制构造函数匹配。通过替换到构造函数模板创建的复制构造函数将是

    wrapper(wrapper<A>& w) : wrapped_value( w ) {
    }

因为 wrapped_value 属于 A 类型,这是一个错误,因为 A 没有接受 wrapper&lt;A&gt; 的构造函数。

但是“替换失败不是错误”不是吗?因此,当编译器尝试将构造函数模板用作复制构造函数时,构造函数模板会失败——为什么这会阻止自动生成复制构造函数?还是不是,真正的问题与std::vector 的实现有关?

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

  1. 使用“pass-by-value-then-move”而不是完美转发?
  2. 只需将复制构造函数定义为默认值?
  3. 在完美转发构造函数的可变参数之前使用 std::in_place_t 参数?
  4. 在通过enable_if 等进行复制构造的情况下禁用构造函数模板。等。

【问题讨论】:

  • SFINAE 是重载解决方案的一部分。一旦选择了重载并实例化了它的主体(是的,主体在实际调用之前没有实例化),就没有回溯。
  • 所以这不是替换失败?是选择构造函数后报错?
  • 看看 Meyers 的“Effective Modern C++”第 26-27 条,它几乎完全涵盖了你的情况。

标签: c++ constructor variadic-templates perfect-forwarding


【解决方案1】:

替换没有失败,特殊函数的生成没有被阻止。模板替换导致构造函数比编译器生成的复制构造函数更匹配,因此选择它会导致语法错误。

让我们通过摆脱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);

}

在上面的模板替换中 not 失败,因为应用于所需的复制构造函数。我们最终得到了复制构造函数的两个重载,一个由编译器生成,作为特殊函数生成的一部分,另一个是通过将 v1 的类型替换到构造函数模板中生成的:

wrapper(wrapper<A>& rhs); // (1) instantiated from the perfect forwarding template

wrapper(const wrapper<A>& rhs); // (2) compiler-generated ctor.

由于原始代码中的v1 不是const,因此必须选择C++ (1) 的规则。您实际上可以通过将其设置为const 来检查这一点,并且程序将不再无法编译。

至于如何处理,正如 @jcai 在 cmets 中提到的那样,Scott Meyers 在Effective Modern C++ 中的第 27 条是关于如何处理这个问题——基本上它归结为要么不使用完美转发或使用“标签调度”——所以我不会在这里讨论。

【讨论】:

    猜你喜欢
    • 2012-01-27
    • 2020-12-08
    • 1970-01-01
    • 2012-10-14
    • 1970-01-01
    • 2014-05-18
    • 2017-10-05
    • 1970-01-01
    • 2011-03-17
    相关资源
    最近更新 更多