2017-08-15 103 views
14

强制性复制elision是否适用于通过结构化绑定进行分解?下列哪些情况适用于?复制elision是否适用于结构化绑定

// 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<>std::tuple_size<>std::get<>返回xvalues前两个会被“分解”的时候,参数是右值

从标准A报价的话,也很好!

+0

是的,很容易明白为什么当你考虑什么结构绑定实际上desugar。 ; - ] – ildjarn

+0

@ildjarn是你是否确认'one'和'two'不会导致copy elision但是'three'会? – Curious

+1

我的意思是'是的,复制elision和结构化绑定一起工作' - 'one'和'three'将导致有保证的副本省略,'two'不会。 – ildjarn

回答

14

强制性复制elision是否适用于通过结构化绑定进行分解?下列哪些情况适用于?

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

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取决于我们解构的那种类型(数组,元组等,或用类型所有公共非静态数据成员)。

强制复制elision适用于auto __tmp = expr行,其他行都不涉及副本。


有围在注释的例子有些混乱,所以让我阐述在发生的事情:

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都成__tmpdecltype(one)decltype(two)both yield Something而不是Something&&两个右值引用。

+0

https://wandbox.org/permlink/1pRlbWw06mVDguPN这不是我对此的理解。我理解它的方式是,结构化绑定创建一个匿名类/结构,变量的类型与RHS的类型相同(如果存在'tuple_element',那么将用于确定类型),然后执行常规分解过程对于该结构 – Curious

+2

@Curious:结构化绑定不会有这种类型。它根本不创建类。 –

+0

@NicolBolas我知道它没有,那只是我的心智模式。它迄今为止工作... – Curious

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<i, E>::type”:如果其对应的 初始值设定项是左值,则以右值为参考,否则为左值引用。为第i个的标识符的初始化 是

  • e.get<i>(),如果查找使标识符于E的按类别成员访问查找的范围得到找到至少一个声明
  • 否则(的任何 那种), get<i>(e),其中得到的是由参数相关的查找只抬头,从而忽略非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}); 

会导致复制省略。

相关问题