有趣的问题:
#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)
,其中 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});
会导致复制省略。