【问题标题】:Is the behaviour of std::get on rvalue-reference tuples dangerous?std::get 在右值引用元组上的行为是否危险?
【发布时间】:2018-06-02 23:20:06
【问题描述】:

以下代码:

#include <tuple>

int main ()
{
  auto f = [] () -> decltype (auto)
  {
    return std::get<0> (std::make_tuple (0));
  };

  return f ();
}

(静默)生成具有未定义行为的代码 - make_tuple 返回的临时右值通过 std::get 和 decltype(auto) 传播到返回类型。因此,它最终返回对超出范围的临时对象的引用。在这里查看https://godbolt.org/g/X1UhSw

现在,您可能会争辩说我对decltype(auto) 的使用是错误的。但是在我的通用代码中(元组的类型可能是std::tuple&lt;Foo &amp;&gt;)我不想总是复制。我真的很想从元组中提取确切的值或引用。

我的感觉是std::get 的这种过载很危险:

template< std::size_t I, class... Types >
constexpr std::tuple_element_t<I, tuple<Types...> >&& 
get( tuple<Types...>&& t ) noexcept;

虽然将左值引用传播到元组元素上可能是明智的,但我认为右值引用并不适用。

我确信标准委员会对此进行了非常仔细的考虑,但谁能向我解释为什么这被认为是最佳选择?

【问题讨论】:

  • “我认为这不适用于右值引用” - 如果你 forward_as_tuple 就可以。您希望保留值类别。或者,如果您想从函数返回值中提取一个值。
  • 谢谢,但我不确定它是否保留了价值类别。例如,如果我有一个返回std::tuple&lt;int&gt; 的函数并且我在其上调用std::get&lt;0&gt;,我希望得到一个普通的int,而不是int&amp;&amp;。你能给我一个具体的例子吗?
  • 这种get 的重载不是有效的结构化绑定所必需的吗?
  • 我当然愿意被说服,但是我尝试过使用“安全”版本的重载(在传递右值引用元组时只返回朴素的 tuple_element 类型)的示例似乎工作正常。我错过了什么?

标签: c++ c++11 c++14 rvalue-reference stdtuple


【解决方案1】:

考虑以下示例:

void consume(foo&&);

template <typename Tuple>
void consume_tuple_first(Tuple&& t)
{
   consume(std::get<0>(std::forward<Tuple>(t)));
}

int main()
{
    consume_tuple_first(std::tuple{foo{}});
}

在这种情况下,我们知道std::tuple{foo{}} 是一个临时的,它将在consume_tuple_first(std::tuple{foo{}}) 表达式的整个持续时间内存在。

我们希望避免任何不必要的复制和移动,但仍将foo{} 的临时性传播到consume

这样做的唯一方法是让std::get 在使用临时std::tuple 实例调用时返回一个右值引用

live example on wandbox


std::get&lt;0&gt;(std::forward&lt;Tuple&gt;(t)) 更改为std::get&lt;0&gt;(t) 会产生编译错误(如预期)(on wandbox)。


拥有一个按值返回的get 替代方案会导致额外的不必要的移动:

template <typename Tuple>
auto myget(Tuple&& t)
{
    return std::get<0>(std::forward<Tuple>(t));
}

template <typename Tuple>
void consume_tuple_first(Tuple&& t)
{
   consume(myget(std::forward<Tuple>(t)));
}

live example on wandbox


但是谁能向我解释为什么这被认为是最佳选择?

因为它启用了可选的通用代码,可以在访问元组时无缝传播临时右值引用。按值返回的替代方法可能会导致不必要的移动操作。

【讨论】:

  • 啊,我明白了。所以我的返回朴素类型的“safe_get”会导致额外的移动。恕我直言,这是值得为安全付出的代价,但我至少可以看到这是值得商榷的。非常感谢您的回答!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2017-08-21
  • 1970-01-01
  • 1970-01-01
  • 2016-01-28
  • 2010-12-10
  • 2018-07-27
  • 2018-06-14
相关资源
最近更新 更多