【问题标题】:Does copy elision work with structured bindings复制省略是否适用于结构化绑定
【发布时间】:2018-01-23 16:44:30
【问题描述】:

强制复制省略是否适用于通过结构化绑定进行的分解?这适用于以下哪些情况?

// one
auto [one, two] = std::array<SomeClass>{SomeClass{1}, SomeClass{2}};

// two
auto [one, two] = std::make_tuple(SomeClass{1}, SomeClass{2});

// three
struct Something { SomeClass one, two; };
auto [one, two] = Something{};    

我怀疑只有第三种情况允许复制省略,因为前两种情况将通过std::get&lt;&gt;std::tuple_size&lt;&gt; “分解”,std::get&lt;&gt; 在参数为右值时返回 xvalues

引用标准也很好!

【问题讨论】:

  • 是的,当您考虑结构化绑定实际上去糖时,很容易理解为什么。 ;-]
  • @ildjarn by yes 你确认onetwo 不会导致复制省略,但three 会?
  • 我的意思是“是的,复制省略适用于结构化绑定”——onethree 将导致保证复制省略,two 不会。
  • 但是那些绑定不是标准的?并且数组需要额外的参数
  • @ildjarn 但是只允许通过get&lt;&gt;tuple_size 函数/方法/特征访问?从看起来右边的东西(从我通过阅读标准可以看出)必须是一个数组或一个具有所有公共成员的类,结构化绑定可以以这种方式递归吗?跨度>

标签: c++ c++17 rvalue copy-elision structured-bindings


【解决方案1】:

强制复制省略是否适用于通过结构化绑定进行的分解?这适用于以下哪些情况?

是的,所有这些。结构化绑定的重点是为您提供对您要绑定的类型的解构元素的命名引用。这个:

auto [one, two] = expr;

只是语法糖:

auto __tmp = expr;
some_type<0,E>& a = some_getter<0>(__tmp);
some_type<1,E>& b = some_getter<1>(__tmp);

其中some_typesome_getter 取决于我们要解构的类型(数组、类元组或具有所有公共非静态数据成员的类型)。

强制复制省略适用于auto __tmp = expr 行,其他行均不涉及复制。


围绕 cmets 中的示例存在一些混淆,所以让我详细说明一下:

auto [one, two] = std::make_tuple(Something{}, Something{});

那个expands into

auto __tmp = std::make_tuple(Something{}, Something{}); // note that it is from
// std::make_tuple() itself that we get the two default constructor calls as well
// as the two copies.
using __E = std::remove_reference_t<decltype(__tmp)>; // std::tuple<Something, Something>

然后,由于__Enot an array typeis tuple-like,我们通过unqualified call to get looked up in the associated namespace of __E 引入变量。初始化器是xvalue,类型是rvalue references

std::tuple_element_t<0, __E>&& one = get<0>(std::move(__tmp));
std::tuple_element_t<1, __E>&& two = get<1>(std::move(__tmp));

请注意,虽然onetwo 都是__tmp 的右值引用,但decltype(one)decltype(two) 将是both yield Something 而不是Something&amp;&amp;

【讨论】:

  • @Curious:结构化绑定没有任何作用。它根本不创建类。
  • Nitpick:带有双下划线 (__tmp) 的名称被保留 - 不要在示例中使用此类名称。
  • @Curious make_tuple(和一般的tuple)在创建元组时需要具体化临时对象以绑定到make_tuple(或tuple 的构造函数)的引用参数。这与结构化绑定无关。
  • @JesperJuhl 我在示例中使用带有双下划线的名称来说明语言引入的名称,这与标准使用 __range__begin__end 定义基于范围的 for 语句的含义。
  • @JesperJuhl 旨在说明编译器如何对结构化绑定声明进行脱糖。并且编译器最好为它引入的变量使用保留名称!
【解决方案2】:

有趣的问题:

#include <iostream>
#include <array>
#include <tuple>
#include <typeinfo>
using std::cout;
using std::endl;

struct SomeClass
{
    int baz;

    SomeClass(int _b): baz(_b) {
        cout << __PRETTY_FUNCTION__ << " = " << baz << endl;
    }
    SomeClass(SomeClass&&) {
        cout << __PRETTY_FUNCTION__ << endl;
    }
    SomeClass(const SomeClass&) {
        cout << __PRETTY_FUNCTION__ << endl;
    }
};

template<typename T> void tell(T&& a)
{
    cout << "Tell: " << __PRETTY_FUNCTION__ << " = " << a.baz << endl;
}

int main()
{
     // one
     cout << "= 1 =" << endl;
     auto [one, two] = std::array<SomeClass,2>{SomeClass{1}, SomeClass{2}};
     cout << "===" << endl;
     tell(one); tell(two);
     // two
     cout << endl << "= 2 =" << endl;
     auto [one2, two2] = std::make_tuple(SomeClass{1}, SomeClass{2});
     cout << "===" << endl;
     tell(one2); tell(two2);
     // three
     cout << endl << "= 3 =" << endl;
     struct Something { SomeClass one{1}, two{2}; };     
     auto [one3, two3] = Something{}; 
     cout << "===" << endl;
     tell(one3); tell(two3);

    return 0;
}

产生输出:

= 1 =
SomeClass::SomeClass(int) = 1
SomeClass::SomeClass(int) = 2
===
Tell: void tell(T&&) [with T = SomeClass&] = 1
Tell: void tell(T&&) [with T = SomeClass&] = 2

= 2 =
SomeClass::SomeClass(int) = 2
SomeClass::SomeClass(int) = 1
SomeClass::SomeClass(SomeClass&&)
SomeClass::SomeClass(SomeClass&&)
===
Tell: void tell(T&&) [with T = SomeClass&] = 0
Tell: void tell(T&&) [with T = SomeClass&] = 4199261

= 3 =
SomeClass::SomeClass(int) = 1
SomeClass::SomeClass(int) = 2
===
Tell: void tell(T&&) [with T = SomeClass&] = 1
Tell: void tell(T&&) [with T = SomeClass&] = 2

第二种情况使用复制或移动(如果可用)构造函数。没有初始化值,因为我故意没有在构造函数中这样做。

有三种绑定协议

  • 绑定到数组
  • 绑定到类似元组的类型
  • 绑定到公共数据成员

在第二种情况下(对不起,我无权访问 C++17 pdf,所以 cppreference):

每个标识符都成为一个变量,其类型是“引用 std::tuple_element&lt;i, E&gt;::type": 对应的左值引用 初始化器是左值,否则是右值引用。初始化器 对于第 i 个标识符是

  • e.get&lt;i&gt;(),如果通过类成员访问查找在 E 范围内查找标识符时发现至少一个声明(无论 种类)
  • 否则,get&lt;i&gt;(e),其中 get 仅通过依赖于参数的查找来查找,忽略非 ADL 查找

示例的第一阶段和第二阶段实际上是对类元组类型的绑定。 但是......在第二阶段,我们用什么来初始化?一个构造元组的模板函数:

 std::make_tuple(SomeClass{1}, SomeClass{2});

实际上会复制或移动值。可能会发生进一步的复制省略,但是

 auto t = std::make_tuple(SomeClass{1}, SomeClass{2});
 auto [one2, two2] = t;

会产生这个输出:

SomeClass::SomeClass(int) = 2
SomeClass::SomeClass(int) = 1
SomeClass::SomeClass(SomeClass&&)      //make_tuple
SomeClass::SomeClass(SomeClass&&)
SomeClass::SomeClass(const SomeClass&) //assignment 
SomeClass::SomeClass(const SomeClass&)

虽然适当地去糖结构化绑定看起来像:

 auto t = std::make_tuple(SomeClass{1}, SomeClass{2});
 auto& one2 = std::get<0>(t);
 auto& two2 = std::get<1>(t);

并且输出与原始匹配:

SomeClass::SomeClass(int) = 2
SomeClass::SomeClass(int) = 1
SomeClass::SomeClass(SomeClass&&)
SomeClass::SomeClass(SomeClass&&)
===

因此,发生的复制或移动操作来自构造我们的tuple。 我们会避免这种情况,如果我们使用通用引用构造元组,那么两者都会去糖

 auto t = std::tuple<SomeClass&&, SomeClass&&>(SomeClass{1}, SomeClass{2});
 auto& one2 = std::get<0>(t);
 auto& two2 = std::get<1>(t);

结构化绑定

 auto [one2, two2] = std::tuple<SomeClass&&, SomeClass&&>(SomeClass{1}, SomeClass{2});

会导致复制省略。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-01-27
    • 2018-10-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多