【问题标题】:std::tuple default constructor with move-constructable element带有可移动构造元素的 std::tuple 默认构造函数
【发布时间】:2019-10-29 20:10:15
【问题描述】:

我正在尝试返回一个std::tuple,其中包含一个不可复制构造类型的元素。这似乎阻止了我使用默认的类构造函数来构造元组。例如,要返回一个包含Foo 的元组,必须创建一个foo 实例和std::moved:

class Foo {
  public:
    Foo(const Foo&) = delete;
    Foo(Foo&&) = default;
    int x;
};

tuple<int, Foo> MakeFoo() {
    Foo foo{37};
//  return {42, {37}}; // error: could not convert ‘{42, {37}}’ from ‘’ to ‘std::tuple’
    return {42, std::move(foo)};
}

另一方面,如果类被定义为具有复制构造函数,则元组的构造可以正常工作:

class Bar {
  public:
    Bar(const Bar&) = default;
    int x;
};

tuple<int, Bar> MakeBar() {
    return {42, {37}}; // compiles ok
}

有没有办法将MakeBar 语法与Foo 类一起使用?

【问题讨论】:

  • return {42, Foo{37}}; 呢?
  • @super 这是一个改进,但我仍然想了解为什么编译器无法推断出这种用法。

标签: c++ c++17 stdtuple


【解决方案1】:

不完全是,但你有(至少)两个选择。

你可以拼出Foo

tuple<int, Foo> MakeFoo() {
    return {42, Foo{37}}
}

或者您可以向Foo 添加一个构造函数来替换您现在正在执行的聚合初始化:

class Foo {
  public:
    Foo(const Foo&) = delete;
    Foo(Foo&&) = default;
    Foo(int x) : x(x) {}      // <--
    int x;
};

tuple<int, Foo> MakeFoo() {
    return {42, 37};
}

但是为什么写return {42, 37} 而不是return {42, {37}}?为什么需要添加一个构造函数来完成这项工作?

用至少一种只移动类型构造一个元组意味着我们不能使用直接构造函数

tuple<Types...>::tuple(const Types &...)

相反,我们必须使用模板化转换构造函数

template<class... UTypes>
tuple<Types...>::tuple(UTypes &&...)

所以会推导出两个参数的类型。但是,{37} 是一个初始化列表,在这种情况下,它使第二个函数参数成为 非推导上下文(参见 [temp.deduct.type]/5.6)。所以模板参数推导失败,构造函数不能被调用。所以,我们必须改写return {42, 37} 才能让推理成功。

此外,此模板化转换构造函数仅在参数类型可转换 为相应的元组元素类型时参与重载决议。并且可转换性不考虑聚合初始化,因此int 不能转换为原始Foo。但是如果我们添加转换构造函数Foo::Foo(int),则int现在可以转换为Foo,并且可以调用tuple构造函数。

【讨论】:

  • 不幸的是,在我的情况下,Foo 是在生成的代码中定义的,不能轻易修改。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-12-16
  • 1970-01-01
  • 2018-10-04
  • 1970-01-01
  • 2023-04-08
相关资源
最近更新 更多