简体   繁体   English

在通用C ++代码中移动基于范围的循环?

[英]Moving in range-based loop in generic C++ code?

Imagine that you have this generic pseudo-code: 想象一下,你有这个通用的伪代码:

template<typename Iterable>
void f(Iterable&& iterable)
{
   ...
}

We want to handle rvalue and lvalue references to iterable objects 1 , and the idea is that the function handles the container performing operations element by element. 我们想要处理对可迭代对象1的 rvalue和lvalue引用,并且想法是该函数处理容器逐元素地执行操作。

It is plausible that we want to forward the reference specification of the container to the elements. 我们想要将容器的参考规范转发给元素是合理的。 In other words, if iterable is an rvalue reference, the function will have to move the elements from the container. 换句话说,如果iterable是一个右值引用,则该函数必须从容器中移动元素。

Using C++17, I would do 使用C ++ 17,我会这样做

auto [begin, end] = [&] {
    if constexpr(std::is_lvalue_reference_v<Iterable>)
        return std::array{std::begin(iterable),
                          std::end(iterable)};
    else
        return std::array{
            std::make_move_iterator(std::begin(iterable)),
            std::make_move_iterator(std::end(iterable))};
}();
std::for_each(begin, end, [&](auto&& element)
{
    ...
});

Obviously, this is not the best code to maintain 2 , error prone and probably not so easy to optimize for the compiler. 显然,这不是维护2的最佳代码,容易出错,并且编译器可能不那么容易优化。

My question is: it could be possible, for future C++ standards, to introduce the concept of forwarding range-based loops ? 我的问题是:对于未来的C ++标准,有可能引入转发基于范围的循环的概念吗? It would be nice if this 如果这样会好的

for(auto&& el : std::move(iterable))
{
    ...
}

could handle el as rvalue reference. 可以处理el作为右值参考。 In this way, this would be possible: 通过这种方式,这将是可能的:

template<typename Iterable>
void f(Iterable&& iterable)
{
    for(auto&& el : std::forward<Iterable>(iterable))
    {
        /*
         *  el is forwarded as lvalue reference if Iterable is lvalue reference,
         *  as rvalue reference if Iterable is rvalue reference
         */
        external_fun(std::forward<decltype(el)>(el));
    }
}

I am concerned about code-breaking changes, but at the same time I am not able to think about situations in which passing a rvalue reference as argument of a range based loop is expected to work without moving objects. 我担心代码破坏性的变化,但与此同时,我无法考虑将rvalue引用作为基于范围的循环的参数传递而不需要移动对象的情况。

As suggested, I tried to write down how I would change the 6.5.4 section of the standard. 正如所建议的那样,我试着写下如何更改标准的6.5.4部分。 The draft can be read at this address . 草案可以在这个地址阅读。

Do you think that it would be possible to introduce this feature without introducing serious issues? 您是否认为可以在不引入严重问题的情况下引入此功能?

1 Checked with C++20 concepts or static_asserts 1使用C ++ 20概念或static_asserts进行检查
2 And it's quite worse without C++17 2没有C ++ 17就更糟糕了

This won't work. 这不行。 Fundamentally there are two kinds of things you can iterate over: those that own the elements, and those that don't. 从根本上说,你可以迭代两种东西:那些拥有元素的东西,以及那些没有元素的东西。 For non-owning ranges, the value category of the range is immaterial. 对于非拥有范围,范围的值类别并不重要。 They don't own their elements and so you can't safely move from them. 他们没有自己的元素,所以你不能安全地离开他们。 The range-based for loop must work with both kind of ranges. 基于范围的for循环必须适用于两种范围。

There are also corner cases to consider (eg, proxy iterators). 还有一些需要考虑的极端情况(例如,代理迭代器)。 The range-based for loop is basically syntax sugar that imposes only a very minimal set of requirements on the thing being iterated over. 基于范围的for循环基本上是语法糖,它只对被迭代的东西强加了一组非常小的要求。 The benefit is that it can iterate over lots of things. 好处是它可以迭代很多东西。 The cost is that it doesn't have much room to be clever. 成本是它没有太多聪明的空间。


If you know that the iterable in fact owns its elements (so that moving is safe), then all you need is a function that forwards something according to the value category of some other thing: 如果你知道iterable实际上拥有它的元素(这样移动是安全的),那么你需要的只是一个根据其他东西的值类别转发东西的函数:

namespace detail {
    template<class T, class U>
    using forwarded_type = std::conditional_t<std::is_lvalue_reference<T>::value,
                                              std::remove_reference_t<U>&, 
                                              std::remove_reference_t<U>&&>;
}
template<class T, class U>
detail::forwarded_type<T,U> forward_like(U&& u) {
    return std::forward<detail::forwarded_type<T,U>>(std::forward<U>(u));
}

You may add a wrapper, something like: 你可以添加一个包装器,如:

template <typename T> struct ForwardIterable;

template <typename T> struct ForwardIterable<T&&>
{
    ForwardIterable(T&& t) : t(t) {}
    auto begin() && { return std::make_move_iterator(std::begin(t)); }
    auto end() && { return std::make_move_iterator(std::end(t)); }

    T& t;
};

template <typename T> struct ForwardIterable<T&>
{
    ForwardIterable(T& t) : t(t) {}
    auto begin() { return std::begin(t); }
    auto end() { return std::end(t); }
    auto begin() const { return std::begin(t); }
    auto end() const { return std::end(t); }

    T& t;
};

template <typename T>
ForwardIterable<T&&> makeForwardIterable(T&& t)
{
    return {std::forward<T>(t)};
}

And then 接着

for(auto&& el : makeForwardIterable(std::forward(iterable)))
{
    // ...
}

your suggestion will introduce breaking changes. 你的建议将引入重大变化。 Assume this piece of code: 假设这段代码:

vector<unique_ptr<int>> vec;
for (int i = 0; i < 10; ++i)
    vec.push_back(make_unique<int>(rand()%10));

for (int i = 0; i < 2; ++i) {
    for (auto &&ptr : move(vec))
        cout << (ptr ? *ptr : 0) << " ";
    cout << endl;
}

With current standard, it'll print two same lines 按照现行标准,它将打印两条相同的线

Write a simple range type. 写一个简单的范围类型。 It stores two iterators and exposes begin() and end() . 它存储两个迭代器并公开begin()end()

Write a move_range_from(Container&&) function that returns a range of move iterators. 编写一个move_range_from(Container&&)函数,它返回一系列移动迭代器。

Write move_range_from_if<bool>(Container&&) that creates a range or moves from the range conditionally. move_range_from_if<bool>(Container&&)创建一个范围或有条件地从该范围移动。

Support lifetime extension in both. 支持两者的终身延长。

template<typename Iterable>
void f(Iterable&& iterable) {
  auto move_from = std::is_rvalue_reference<Iterable&&>{};
  for(auto&& e: move_range_from_if< move_from >(iterable) ) {
  }
}

does what you want. 做你想要的。

This supports both ranges (non-owning) and containers and doesn't require a language extension. 这支持范围(非拥有)和容器,不需要语言扩展。 And it doesn't break existing code. 并且它不会破坏现有代码。

The lifetime extension feature is so you can call these functions with prvalues; 生命周期扩展功能是你可以使用prvalues调用这些函数; without it, for(:) loops don't lifetime extend arguments to the loop target function call. 没有它, for(:)循环不会将生命周期扩展参数传递给循环目标函数调用。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM