17

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

结构化绑定

到目前为止,有一个已知的技巧是滥用 std::tie 直接将元组或对分配给不同的变量,而不必手动处理结果类型。这是一个 hack,变量也必须存在,现在您可以在一行中声明变量并初始化它们:

自动 [a , b , c] = getvalues();

需要大括号,getvalues 返回一个元组。提案中没有提到 std::pair ,因此不清楚这是否适用于 pair ,它是由 STL 在某些插入方法中返回的。

我假设他们指的是这种用法std::tie

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

我认为这是推荐的做法。

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

4

4 回答 4

38

我可以这么简单地说:

在函数只能返回一个变量的语言中

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)

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

std::tie是一个缺少语言功能的库黑客。它有一些缺点:

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

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

什么不见​​了?

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

在哪里推导出类型并且a是显式的“std::string”cb

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

其中第二个返回的对象foo是一个tuple类似的对象。

  • 返回站点的语言功能(std::tuple全部绕过):
auto foo() -> [int, int]

代替

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

……嗯……那不是很好吗

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

auto [min = *min_it, max = *max_it] = minmax_element(...);
于 2016-10-25T13:37:17.917 回答
6

一个非常明显的区别是 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
于 2020-06-18T12:08:06.203 回答
5

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

它用于创建一个引用变量的元组

创建对其参数或 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);
}

这里创建了元组,但它们不复制变量但有引用。

现在因为他们持有引用,你可以“破解”它来做这样的事情

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

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

这甚至在刚刚提到的“注释”中出现在 cpprefence 上

std::tie 可用于解包 std::pair 因为 std::tuple 具有从对转换的赋值

在 c++17 中,他们引入了“结构化绑定”来处理一次分配多个变量的场景。因此,无论是故意的还是黑客攻击,从 c++17 开始,这种 tie 的使用不再是必要的了。

无论std::tie是打算以这种方式使用还是“黑客”可能是个人意见,我想介绍的人std::tie最了解这一点。但考虑到结构化绑定std::tie在这种情况下的替代方式,他们想出了一个他们认为更好的解决方案。

于 2016-10-25T13:56:54.270 回答
1

我希望没有人介意我把我的意见混在一起,因为我认为它仍然有效。我同意很多说法,但我认为结构化绑定不会取代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.

如果变量不存在,那么结构化绑定是您最好的朋友,但如果存在,它们根本就没有帮助。此外,正如有人提到的那样,结构化绑定还有许多其他遗漏,对我来说,这意味着它们缺乏。首先是嵌套它们的能力,使得其中一个变量本身可能是一个元组/对等。其次,虽然这有点不同,但无法在 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仍然具有结构化绑定可以提供的非常有用的目的,但事实并非如此。

于 2021-01-29T07:14:21.867 回答