背景和上一个搜索
我正在寻找一种优雅的方法来在C ++ 14中使用基于范围的for循环在容器(例如std :: vector)上反向迭代。在寻找解决方案时,我发现了this Q/A。它基本上告诉我,这不是标准库的一部分,我必须自己使用boost或实现适配器。我不想使用boost,所以我现在正在寻找自己最好的实现。
除了previously mentioned Q/A中给出的建议外,我还发现了this implementation和this blog关于该主题的信息。大多数实现都非常相似,而且看起来还不错。但是它们都有一个陷阱:如this comment中所指出的,如果用一个临时对象调用反向适配器,则可能会导致悬挂的引用结束:
for (const auto& v : reverse_iterate(getContainer()))
关于基于范围的for循环中的临时对象的问题,this answer确实帮助了我的理解。但是,我们该怎么做才能防止引用晃动?
我的解决方案
基于此背景,我正在寻找一种摆脱这种陷阱的实现。在以下实现中,我使用附加的右值引用rx_
来延长输入参数的寿命,当使用右值引用调用iff reverse_iterate
时。
编辑:请勿使用此解决方案。正如公认的解决方案所指出的那样,这是错误的。
template <typename T>
class reverse_range
{
T &&rx_; // rvalue-reference to prolong livetime of temporary object
T &x_; // reference to container
public:
explicit reverse_range(T &x) : rx_(T{}), x_(x) {}
explicit reverse_range(T &&rx) : rx_(std::move(rx)), x_(rx_) {}
auto begin() const -> decltype(this->x_.rbegin())
{
return x_.rbegin();
}
auto end() const -> decltype(this->x_.rend())
{
return x_.rend();
}
};
template <typename T>
reverse_range<T> reverse_iterate(T &x)
{
return reverse_range<T>(x);
}
template <typename T>
reverse_range<T> reverse_iterate(T &&rx)
{
return reverse_range<T>(std::move(rx));
}
显然,在lvalue构造函数中构造一个未使用的空容器对象会产生一些开销,但是我认为这还不错。另外,可以通过提供两个类reverse_range_lvalue
和reverse_range_rvalue
来摆脱这种情况,这两个类分别为一种参数类型提供实现...
问题
以上扩展程序是否可以解决悬而未决的参考问题,或者我会错过什么?
关于我的代码的其他问题,您有什么提示吗?
在C ++ 14或任何其他(未来)版本中是否有更好的想法来解决此问题?
答案 0 :(得分:13)
那是行不通的。生命周期扩展不适用于构造函数的该部分。 (它在构造函数的主体中起作用,而不是在成员初始化器列表中起作用)。
template<class R>
struct backwards_t {
R r;
constexpr auto begin() const { using std::rbegin; return rbegin(r); }
constexpr auto begin() { using std::rbegin; return rbegin(r); }
constexpr auto end() const { using std::rend; return rend(r); }
constexpr auto end() { using std::rend; return rend(r); }
};
// Do NOT, I repeat do NOT change this to `backwards_t<std::decay_t<R>>.
// This code is using forwarding references in a clever way.
template<class R>
constexpr backwards_t<R> backwards( R&& r ) { return {std::forward<R>(r)}; }
这会在传递右值时移动,并在传递左值时保留引用。
诀窍在于,对于转发引用T&&
,如果T&&
是左值,则T
是引用,而如果T&&
是右值,则{{1 }}是一个值。因此,我们在将左值转换为值时将左值转换为引用(并且不进行复制)(并将右值移至所述值)。
T
这样行得通。
在c++17中,您可以做得更好一些;如果进行聚合初始化,则引用生存期扩展名可以应用于结构的内容。但是我建议不要这样做。延长参考寿命很脆弱,而且很危险。
在c++20或更高版本中有讨论允许编译器将移动对象转换为过期对象。但是我不会打赌它可以在特定情况下工作。我还认为我看到了一篇论文,其中涉及用其生命周期依赖项信息标记ctor和函数(即,返回值取决于参数的生命周期),允许警告/错误以及可能更通用的生命周期延长。
所以这是一个已知的问题。但这是今天解决此问题的最好的总体上安全的方法。