就特定视图的使用而言,std::ranges::algorithm 与基于范围的 for() 循环相比在语义上有所不同

Fur*_*ish 6 c++ for-loop std-ranges c++23

我想逐列查看 3x3 网格,所以我想我会std::views::stride像这样使用:

#include <array>
#include <iostream>
#include <ranges>

auto main() -> int {
    auto grid = std::array<std::array<int, 3>, 3>{{
            {1, 2, 3},
            {4, 5, 6},
            {7, 8, 9}
    }};

    namespace vs = std::views;

    auto strideCount = grid.size();
    auto allElements = grid | vs::join;

    auto columns = vs::iota(0uz, strideCount)
                   | vs::transform([allElements, strideCount](auto n) {
                        return allElements
                                | vs::drop(n)
                                | vs::stride(strideCount);
                   });

    for (auto&& column : columns) {
        for (auto element : column) {
            std::cout << element << ' ';
        }
        std::cout << '\n';
    }
}
Run Code Online (Sandbox Code Playgroud)

这可以工作并打印:

1 4 7
2 5 8
3 6 9
Run Code Online (Sandbox Code Playgroud)

也就是说,它逐列打印每个元素。

在我最初的示例中,我想测试每列是否满足给定的标准。for ()我尝试使用:而不是基于范围的循环std::ranges::all_of()

1 4 7
2 5 8
3 6 9
Run Code Online (Sandbox Code Playgroud)

唯一的区别是:

#include <algorithm>
#include <array>
#include <iostream>
#include <ranges>

auto main() -> int {
    auto grid = std::array<std::array<int, 3>, 3>{{
            {1, 2, 3},
            {4, 5, 6},
            {7, 8, 9}
    }};

    namespace vs = std::views;

    auto strideCount = grid.size();
    auto allElements = grid | vs::join;

    auto columns = vs::iota(0uz, strideCount)
                   | vs::transform([allElements, strideCount](auto n) {
                        return allElements
                                | vs::drop(n)
                                | vs::stride(strideCount);
                   });

    std::ranges::all_of(columns, [](auto&& column) {
        for (auto element : column) {
            // logic that alters whether we return true or false
        }
        return true;
    });
}
Run Code Online (Sandbox Code Playgroud)

我现在有:

for (auto&& column : columns) {
    for (auto element : column) {
        std::cout << element << ' ';
    }
    std::cout << '\n';
}
Run Code Online (Sandbox Code Playgroud)

有了适当的#include <algorithm>.

然而,不幸的是这不起作用。它在这里失败(在std::ranges::all_of()片段中):

std::ranges::all_of(columns, [](auto&& column) {
    for (auto element : column) {
        // logic that alters whether we return true or false
    }
    return true;
});
Run Code Online (Sandbox Code Playgroud)

错误地声称:

error: passing 'const std::ranges::stride_view<std::ranges::drop_view<std::ranges::join_view<std::ranges::ref_view<std::array<std::array<int, 3>, 3> > > > >' as 'this' argument discards qualifiers [-fpermissive]
Run Code Online (Sandbox Code Playgroud)

对于两者begin()end().

但是,如果我不接受auto&& columnlambda auto column,代码就会编译。

为什么会这样呢?为什么我可以对循环示例中的每一列使用转发引用for (),但在某些情况下不能在 lambda 参数中执行此操作std::ranges::algorithm

我正在使用gcc version 13.1.0 (MinGW-W64 x86_64-msvcrt-mcf-seh, built by Brecht Sanders)(的输出g++ -v)。

康桓瑋*_*康桓瑋 6

这是 LWG 3996P2997特别提供了建议的措辞。


ranges::all_of要求谓词Pred满足indirect_unary_predicate<Pred, projected<I, Proj>>,最终导致

invocable<Pred&, iter_common_reference_t<projected<I, Proj>>> 
Run Code Online (Sandbox Code Playgroud)

必须满足,其中Proj是投影函数。

类模板是一种辅助类型,用于构造新的迭代器类型,其引用类型是(在大多数情况下)std::projected应用于 的结果,这有助于拼写“范围化”算法的要求([投影]):Projstd::identityiter_reference_t<I>

namespace std {
  template<class I, class Proj>
  struct projected-impl {                               // exposition only
    struct type {                                       // exposition only
      using value_type = remove_cvref_t<indirect_result_t<Proj&, I>>;
      using difference_type = iter_difference_t<I>;     // present only if I
                                                        // models weakly_incrementable
      indirect_result_t<Proj&, I> operator*() const;    // not defined
    };
  };

  template<indirectly_readable I, indirectly_regular_unary_invocable<I> Proj>
    using projected = projected-impl<I, Proj>::type;
}
Run Code Online (Sandbox Code Playgroud)

让我们回到invocable<Pred&, iter_common_reference_t<projected<I, Proj>>>.

在您的示例中,原始迭代器Ireference都是value_typeprvalue stride_view,因此其公共引用iter_common_reference_t<I>也是stride_view

然而,projected<I, Proj>和分别referencevalue_type和,这使得最终stride_view&&的计算stride_view结果为。由于是 不可迭代的,这会导致实例化期间出现硬错误,因为我们在不受约束的 lambda 内部调用's 。iter_common_reference_t<projected<I, Proj>>const stride_view&stride_viewconstconst stride_viewbegin

这是一个标准缺陷。看来我们仍然需要 LWG 3859projected<I, std::identity>的拟议决议来制定I,尽管它被标记为“由P2609R3解决”,但本例并非如此。

解决方法是使用identity返回纯右值的虚拟对象,以便投影迭代器的公共引用仍然是纯右值(demo):

std::ranges::all_of(columns, [](auto&& column) {
    for (auto element : column) {
        // logic that alters whether we return true or false
    }
    return true;
}, [](auto r) { return r; });
Run Code Online (Sandbox Code Playgroud)