避免为基于反向范围的for循环实现悬挂参考

时间:2018-10-09 13:34:36

标签: c++ for-loop c++14 rvalue-reference

背景和上一个搜索

我正在寻找一种优雅的方法来在C ++ 14中使用基于范围的for循环在容器(例如std :: vector)上反向迭代。在寻找解决方案时,我发现了this Q/A。它基本上告诉我,这不是标准库的一部分,我必须自己使用boost或实现适配器。我不想使用boost,所以我现在正在寻找自己最好的实现。

除了previously mentioned Q/A中给出的建议外,我还发现了this implementationthis 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_lvaluereverse_range_rvalue来摆脱这种情况,这两个类分别为一种参数类型提供实现...

问题

以上扩展程序是否可以解决悬而未决的参考问题,或者我会错过什么?

关于我的代码的其他问题,您有什么提示吗?

在C ++ 14或任何其他(未来)版本中是否有更好的想法来解决此问题?

1 个答案:

答案 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

这样行得通。

中,您可以做得更好一些;如果进行聚合初始化,则引用生存期扩展名可以应用于结构的内容。但是我建议不要这样做。延长参考寿命很脆弱,而且很危险。

或更高版本中有讨论允许编译器将移动对象转换为过期对象。但是我不会打赌它可以在特定情况下工作。我还认为我看到了一篇论文,其中涉及用其生命周期依赖项信息标记ctor和函数(即,返回值取决于参数的生命周期),允许警告/错误以及可能更通用的生命周期延长。

所以这是一个已知的问题。但这是今天解决此问题的最好的总体上安全的方法。