STD的行为是危险的
Is the behaviour of std::get on rvalue-reference tuples dangerous?
以下代码:
#include <tuple>
int main ()
{
auto f = [] () -> decltype (auto)
{
return std::get<0> (std::make_tuple (0));
};
return f ();
}
(默默地)以未定义的行为生成代码 - make_tuple
返回的临时RVALUE通过std :: get&lt;>传播到返回类型。因此,它最终返回了一个临时范围的引用。在此处查看https://godbolt.org/g/x1uhsw。
现在,您可以说我对decltype(auto)
的使用有错。但是在我的通用代码(其中可能是std::tuple<Foo &>
的类型)中,我不想始终进行副本。我确实想从元组中提取确切的值或参考。
我的感觉是std::get
的超载很危险:
template< std::size_t I, class... Types >
constexpr std::tuple_element_t<I, tuple<Types...> >&&
get( tuple<Types...>&& t ) noexcept;
虽然将LVALUE引用传播到元组元素上可能是明智的,但我认为这不适合RVALUE参考。
我确定标准委员会非常仔细地考虑这一点,但是谁能向我解释为什么这被认为是最好的选择?
考虑以下示例:
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
实例调用时返回 rvalue参考。
wandbox上的实时示例
将std::get<0>(std::forward<Tuple>(t))
更改为std::get<0>(t)
产生汇编错误(如预期)( 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)));
}
wandbox上的实时示例
但是谁能向我解释为什么这被认为是最好的选择?
因为它可以启用可选的通用代码,以无缝传播临时性 rvalue参考访问元组时。按值返回的替代方案可能会导致不必要的移动操作。
imho这是危险且非常可悲的,因为它破坏了"最重要的const
"的目的:
通常,临时对象仅持续到其出现的完整表达式的末端。但是,C 故意指定将临时对象与堆栈上的参考结合到参考的参考,从而将临时寿命延长到参考本身的寿命,因此避免了否则会是常见的悬空引用误差。
鉴于上述引用,多年来,C 程序员已经了解到这是可以的:
X const& x = f( /* ... */ );
现在,考虑此代码:
struct X {
void hello() const { puts("hello"); }
~X() { puts("~X"); }
};
auto make() {
return std::variant<X>{};
}
int main() {
auto const& x = std::get<X>(make()); // #1
x.hello();
}
我相信任何人都应该原谅,以为第1行是可以的。但是,由于std::get
返回将要破坏的对象的引用,因此x
是一个悬空的参考。上面的代码输出:
~X
hello
表明,在调用hello()
之前,x
绑定的对象被破坏。Clang对问题发出了警告,但GCC和MSVC没有。如果(如在OP中)我们使用std::tuple
而不是std::variant
,则会发生同样的问题,但可悲的是,Clang不会发出警告。
std::optional
和此value
过载也发生了类似的问题:
constexpr T&& value() &&;
使用上面使用相同X
的代码说明了问题:
auto make() {
return std::optional{X{}};
}
int main() {
auto const& x = make().value();
x.hello();
}
输出为:
~X
~X
hello
与C 23的std::except
及其方法value()
和error()
相同,将自己提供更多相同的功能。
constexpr T&& value() &&;
constexpr E&& error() && noexcept;
我宁愿在维托里奥·罗密欧(Vittorio Romeo)的帖子中付出这一举动的代价。当然,我可以从第1行和#2中删除&
来避免问题。我的观点是"最重要的const
"规则。只是变得更加复杂,我们需要考虑表达是否涉及std::get
,std::optional::value
,std::expected::value
,std::expected::error
,...
- 使用std::multimap迭代器创建std::list
- C++中std::resize(n)和std::shrink_to_fit之间的区别
- 来自 std::list 的迭代器 .end() 按预期返回"0xcdcdcdcdcdcdcdcd"但 .begin()
- C++17复制构造函数,在std::unordereded_map上进行深度复制
- 如何导出包含具有"std::unique_ptr"值的"std::map"属性的
- 从持续时间构造std::chrono::system_clock::time_point
- std::具有相同基类的类的变体
- std::向量与传递值的动态数组
- 使用std::vector的OpenCL矩阵乘法
- std::map<struct,struct>::find 找不到匹配项,但是如果我循环通过 begin() 到 end(),我在那里看到匹配项
- std::condition_variable::wait()如何评估给定的谓词
- 如何获取std::result_of函数的返回类型
- std::原子加载和存储都需要吗
- 将对象移动到std::shared_ptr
- POCO::PostgreSQL:如何将std::vector支持添加到`Binder::bind`
- 使用一个考虑到std::map中键值的滚动或换行的键
- 如何从 std::atomic 中提取指针 T<T>?
- 为什么 std::unique 不调用 std::sort?
- STD的行为是危险的
- std::reference_wrapper的隐式T&constructor是否会<T>使其使用起来很危险?