为什么我不能将这个转换后的 directory_iterator 插入到一个向量中?

neo*_*eop 3 c++ stdvector c++20 std-filesystem std-ranges

我正在尝试使用其insert(const_iterator pos, InputIt first, InputIt last)成员函数模板将转换后的目录条目范围插入向量中。不幸的是,我无法在 GCC 下编译以下代码,11.1.0根据https://en.cppreference.com/w/cpp/compiler_support应该有范围支持。

#include <filesystem>
#include <vector>
#include <ranges>
#include <iterator>

namespace fs = std::filesystem;
namespace ranges = std::ranges;
namespace views = std::views;

// no solution
namespace std {
    template <typename F>
    struct iterator_traits<ranges::transform_view<ranges::ref_view<fs::directory_iterator>, F>> {
        using iterator_category = std::input_iterator_tag;
    };
}

int main() {
    std::vector<fs::path> directory_tree;

    auto subdir = fs::directory_iterator(".");
    ranges::input_range auto subdir_names = subdir
        | views::transform([](const auto& entry) { return entry.path(); /* can be more complex*/ })
        | views::common;
    
    // replacing subdir_names with subdir works
    std::input_iterator auto b = ranges::begin(subdir_names);
    std::input_iterator auto e = ranges::end(subdir_names);
    directory_tree.insert(
        directory_tree.begin(),
        b,
        e
    );
}
Run Code Online (Sandbox Code Playgroud)

错误信息主要说:

error: no matching function for call to 'std::vector<std::filesystem::__cxx11::path>::insert(std::vector<std::filesystem::__cxx11::path>::iterator, std::ranges::transform_view<std::ranges::ref_view<std::filesystem::__cxx11::directory_iterator>, main()::<lambda(const auto:16&)> >::_Iterator<false>&, std::ranges::transform_view<std::ranges::ref_view<std::filesystem::__cxx11::directory_iterator>, main()::<lambda(const auto:16&)> >::_Iterator<false>&)'
Run Code Online (Sandbox Code Playgroud)

再往下:

error: no type named ‘iterator_category’ in ‘struct std::iterator_traits<std::ranges::transform_view<std::ranges::ref_view<std::filesystem::__cxx11::directory_iterator>, main()::<lambda(const auto:15&)> >::_Iterator<false> >’
Run Code Online (Sandbox Code Playgroud)

我试图将上述专业化添加到std::iterator_traits相关的迭代器类型,但无济于事。我想了解为什么这不能编译,如果可能的话,如何修复它。我想避免创建临时向量。

让我知道是否需要更多 gcc 的错误消息。

Bar*_*rry 7

fs::directory_iterator是输入范围。这意味着当您通过它进行调整时,transform您仍然可以获得输入范围(自然)。这个转换范围的迭代器有一个后缀operator++,返回void.

这可以说是 C++98 迭代器模型中的一个缺陷,它甚至仍然需要输入迭代器有一个operator++返回原始类型的后缀。即使这必然是一个悬空操作。在 C++20 迭代器模型中,后缀增量可以void为输入迭代器返回。

因此,您返回的转换范围(views::common无操作,它已经commonC++20 输入范围(正如您正在验证的那样),但它不是任何类型的 C++98/C++17 范围因为它的迭代器由于后缀增量规则甚至不满足Cpp17InputIterator - 所以它的迭代器甚至不费心提供iterator_category.

这使得:

directory_tree.insert(directory_tree.begin(), b, e);
Run Code Online (Sandbox Code Playgroud)

失败,因为该功能预计类型满足Cpp17InputIterator,并be别。


解决方法是改为执行以下操作:

ranges::copy(subdir_names, std::inserter(directory_tree, directory_tree.begin()));
Run Code Online (Sandbox Code Playgroud)

或者甚至将两个步骤结合起来:

ranges::copy(subdir
        | views::transform([](const auto& entry) { return entry.path(); /* can be more complex*/ }),
        std::inserter(directory_tree, directory_tree.begin())
);
Run Code Online (Sandbox Code Playgroud)

在这里,我们只要求源范围是 C++20 input_range(它是)。

目的是很快您将能够编写:

directory_tree.insert_range(
        directory_tree.begin(),
        subdir
        | views::transform([](const auto& entry) { return entry.path(); }));
Run Code Online (Sandbox Code Playgroud)

但这不会直到 C++23。