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

dod*_*ndi 2 c++ for-loop generic-programming c++17 c++20

想象一下,你有这个通用的伪代码:

template<typename Iterable>
void f(Iterable&& iterable)
{
   ...
}
Run Code Online (Sandbox Code Playgroud)

我们想要处理对可迭代对象1的 rvalue和lvalue引用,并且想法是该函数处理容器逐元素地执行操作.

我们想要将容器的参考规范转发给元素是合理的.换句话说,如果iterable是右值引用,则该函数必须从容器中移动元素.

使用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)
{
    ...
});
Run Code Online (Sandbox Code Playgroud)

显然,这不是维护2的最佳代码,容易出错,并且编译器可能不那么容易优化.

我的问题是:对于未来的C++标准,有可能引入转发基于范围的循环的概念吗?如果这样会好的

for(auto&& el : std::move(iterable))
{
    ...
}
Run Code Online (Sandbox Code Playgroud)

可以处理el作为右值参考.通过这种方式,这将是可能的:

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));
    }
}
Run Code Online (Sandbox Code Playgroud)

我担心代码破坏性的变化,但与此同时,我无法考虑将rvalue引用作为基于范围的循环的参数传递而不需要移动对象的情况.

正如所建议的那样,我试着写下如何更改标准的6.5.4部分.草案可以在这个地址阅读.

您是否认为可以在不引入严重问题的情况下引入此功能?

1使用C++ 20概念或static_asserts进行检查
2如果没有C++ 17,情况会更糟

T.C*_*.C. 5

这不行.从根本上说,你可以迭代两种东西:那些拥有元素的东西,以及那些没有元素的东西.对于非拥有范围,范围的值类别并不重要.他们没有自己的元素,所以你不能安全地离开他们.基于范围的for循环必须适用于这两种范围.

还有一些需要考虑的极端情况(例如,代理迭代器).基于范围的for循环基本上是语法糖,它对被迭代的事物只强加了一组非常小的要求.好处是它可以迭代很多东西.成本是它没有太多聪明的空间.


如果你知道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));
}
Run Code Online (Sandbox Code Playgroud)

  • @dodomorandi只有一个基于范围的`for`循环,它需要适用于所有类型的迭代 - 包括非常常见的非常规迭代.它知道可迭代的值类别,但不知道迭代是否拥有 - 并且它无法知道 - 它可以安全地对值类别信息做什么是非常有限的. (2认同)