结构化绑定取代 std::tie 滥用

Structured binding to replace std::tie abuse

在阅读 this c++17 最终特性的总结时,我对结构化绑定部分(强调我的)感到有点惊讶:

structured bindings

Until now, there was a known trick to abuse std::tie to assign a tuple or pair to different variables directly, instead of having to deal with the result type manually. This was a hack, and also the variables had to exist, now you can declare the variables and initialize them in one line:

auto [a , b , c] = getvalues();

The braces are needed, getvalues returns a tuple. std::pair is not mentioned in the proposal, so its unclear if this works with pair, which is returned by the STL in some insert methods.

我假设他们指的是 std::tie

的这种用法
int a,b,c;
std::tie(a,b,c) = std::make_tuple(1,2,3);

我认为这是推荐的做法。

有人可以解释为什么他们将上述示例称为 hack 吗?

我可以这么简单地说:

在一种语言中,函数可以return只有一个变量

int a,b,c;
std::tie(a,b,c) = function_returning_multiple_values();

适用于:

auto [a, b, c] = function_returning_multiple_values();

就像在 C++ 只允许一个函数参数的假设世界中一样

int p1, p2, p3;
p1 = ...;
p2 = ...;
p3 = ...;

function_taking_multiple_params(std::tie_params(p1, p2, p3));

将成为黑客:

function_taking_multiple_params(p1, p2, p3)

一个函数最多return一个对象的C++限制你已经习以为常了,但实际上这只是一种人为的语言限制,就像最多接受一个参数的限制一样人为的语言限制。

std::tie 是针对缺失语言功能的库 hack。它有一些缺点:

  • 变量需要事先声明
  • 必须显式声明变量类型
  • 效率低下或不能用于非默认构造的类型

结构化绑定是它们本来可以成为的一切吗?不,但在大多数情况下,它们就是我们所需要的一切。

缺少什么?

  • 某些元素的显式类型:例如:
auto [a, std::string b, c] = foo();

其中 ac 具有推导的类型,而 b 是显式的 "std::string"

  • 嵌套。例如:
auto [a, [b1, b2], c] = foo();

其中来自 foo 的第二个 returned 对象是一个 tuple 类对象。

  • return 站点的语言功能(一起绕过 std::tuple):
auto foo() -> [int, int]

而不是

auto foo() -> std::tuple<int, int>
  • 已命名 return 个对象
auto foo() -> [int& key, int& value]

...嗯...那不是很好吗

  • 并将其与...结合起来 - 准备好一个很酷的新名称 - 通用 return 初始化:
auto minmax_element(It begin, It end) -> [It min_it, It max_it];

auto [min = *min_it, max = *max_it] = minmax_element(...);

std::tie 本身还有另外一个功能。

它是为了创建一个引用变量的元组

Creates a tuple of lvalue references to its arguments or instances of std::ignore.

这对于创建动态元组很有用,而不必复制变量,因为它们是引用。我只是以 cppreference 中的示例作为用例。

bool operator<(const S& rhs) const
{
    // compares n to rhs.n,
    // then s to rhs.s,
    // then d to rhs.d
    return std::tie(n, s, d) < std::tie(rhs.n, rhs.s, rhs.d);
}

此处创建了元组,但它们不复制变量但具有引用。

现在因为它们持有引用,所以您可以"hack"它做这样的事情

int a,b,c;
std::tie(a,b,c) = std::make_tuple(1,2,3);

它将返回的元组的值分配给本身具有引用的元组。

这甚至在刚刚提到的 cpprefence 上也是 "note"

std::tie may be used to unpack a std::pair because std::tuple has a converting assignment from pairs

在 c++17 中,他们引入了 "structured bindings" 来处理一次分配多个变量的场景。因此,无论是有意还是黑客,自从 c++17 以来,不再需要这种领带用法了。

std::tie 是本来就是这样用的还是 "hack" 可能是个人意见,我想介绍 std::tie 的人最清楚这一点。但考虑到在那种情况下结构化绑定是如何取代 std::tie 的,他们想出了一个他们认为更好的解决方案。

一个非常明显的区别是 std::ignore。看例子

std::tuple<string, string> data {"Lord", "Buddha"};
auto [a, b] = data; //valid
auto [ , b] = data; //not valid as the identifier is strongly required
string y;
std::tie( std::ignore, y ) = data; //voila

我希望没有人介意我将我的意见加入其中,因为我认为它仍然有效。我同意很多说法,但我认为结构化绑定不会取代 std::tiestd::tie 有一个特定的用例,您根本无法使用结构化绑定,因为变量是在站点声明的。现在不要误会我的意思,我是结构化绑定的粉丝,但我最近遇到了一个案例,他们只是没有削减它。我有这个结构...

std::vector<std::tuple<std::string, uint32_t, uint64_t>> values;

typedef struct
{
    std::string s;
    uint32_t o;
    uint64_t v;
} A;

std::vector<A> my_objs;

好的,所以我有元组向量和对象向量,我想做的是从元组中获取值并将这些值分配给向量中的每个现有对象,如下所示:

// (This is a very contrived example and you should assume that the
// objects in my_obj are much more complex, already exist and you just want
// to set some values in them)

for (size_t i = 0; i < my_obj.size(); i++)
    std::tie(my_objs.at(i).s, my_objs.at(i).o, my_objs.at(i).v) = values.at(i);

// Sure, you could create a method for my_obj that takes the values, but that's a very
// heavy handed approach and missing the point.

如果变量不存在,那么结构化绑定是您最好的朋友,但如果存在,它们就无济于事。此外,正如有人提到的那样,结构化绑定还有许多其他遗漏,对我来说,这意味着它们缺少。首先是嵌套它们的能力,这样一个变量本身可能是 tuple/pair 等。其次,虽然这有点不同,但无法在 lambda 表达式声明符中使用结构化绑定,如下所示:

std::unordered_map<std::string, Item*> items;

std::for_each(items.begin(), items.end(), [](const auto&[s, item]) { delete item; });   // Not allowed, you have to do...
std::for_each(items.begin(), items.end(), [](const auto& item_pair) { delete item_pair.second; });   // Acceptable

我认为对结构化绑定有用的是声明的变量可以是对现有对象的引用的能力。所以虽然感觉有些人认为std::tie会被结构化绑定取代,但实际情况是std::tie仍然服务结构化绑定可以提供一个非常有用的用途,但实际上没有。