对于简单的 std::ranges 代码,某些编译器存在错误或编译错误

joe*_*ech 11 c++ c++20 std-ranges

我有一段使用 C++20 范围库的代码,取自此 SO anwer。该代码被某些编译器(版本)拒绝,并且某些较旧的 GCC 版本返回垃圾。哪个编译器是正确的?

该代码应该打印 a 中第一列的元素std::vector<std::vector>

#include <vector>
#include <string>
#include <ranges>
#include <iostream>

int main()
{
    // returns a range containing only the i-th element of an iterable container
    auto ith_element = [](size_t i) {
        // drop the first i elements in the range and take the first element from the remaining range
        return std::views::drop(i) | std::views::take(1);
    };

    // returns a range over the i-th column
    auto column = [ith_element](size_t i) {
        return std::views::transform(ith_element(i)) | std::views::join; // returns a range containing only the i-th elements of the elements in the input range
    };


    std::vector<std::vector<std::string>> myvec = {
        { "a", "aaa",   "aa"},
        {"bb",   "b", "bbbb"},
        {"cc",  "cc",  "ccc"}
    };

    for (auto const& v: myvec | column(0)){
        std::cout << v << std::endl;
    }
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

编译器资源管理器

输出:

对于海湾合作委员会 10.1、10.2、10.3、10.4:

b
Run Code Online (Sandbox Code Playgroud)

无法使用 GCC 11.1 和 clang 进行编译。

error: no match for 'operator|' (operand types are 'std::vector<std::vector<std::__cxx11::basic_string<char> > >' and 'std::ranges::views::__adaptor::_Pipe<std::ranges::views::__adaptor::_Partial<std::ranges::views::_Transform, std::ranges::views::__adaptor::_Pipe<std::ranges::views::__adaptor::_Partial<std::ranges::views::_Drop, long unsigned int>, std::ranges::views::__adaptor::_Partial<std::ranges::views::_Take, int> > >, std::ranges::views::_Join>')
Run Code Online (Sandbox Code Playgroud)

预期的

a
bb
cc
Run Code Online (Sandbox Code Playgroud)

在 GCC 11.2、11.3、12.1、12.2、MSVC 19.33 上按预期工作

cpp*_*ner 10

GCC 11.2、11.3、12.1、12.2、MSVC 19.33 是正确的。

Clang up to 15 根本不支持 libstdc++ <ranges>,而使用 libc++ 程序可以正常工作。

GCC 10.1、10.2、10.3、10.4 和 11.1std::views::drop(i)中使用的错误处理ith_element(i)

下面是为什么std::views::drop(i)很复杂,以及 GCC 是如何做错的:

参数如何存储

为了range | std::views::drop(i)工作, 的结果std::views::drop(i)必须记住 的值i。但它应该存储 的副本i还是对 的引用i

当参数是左值时,GCC 10.x 存储引用。因此,对于 GCC 10.x,返回的结果return std::views::drop(i) | std::views::take(1);包含悬空引用。

GCC 11.1 实现了P2281R1 澄清范围适配器对象,这使得此类对象始终存储参数的副本。

大括号与括号初始化

是用大括号 ( ) 还是用小括号 ( )range | std::views::drop(i)初始化结果?std::ranges::drop_view{range, i}std::ranges::drop_view(range, i)

的相关构造函数drop_viewdrop_view(V, range_difference_t<V>). 请注意,第二个参数是有符号整数类型,并且列表初始化不允许无符号到有符号的转换(因为它是缩小转换)。因此,使用大括号时,相应的参数不能是无符号整数(例如size_t)。

GCC 10.x 以某种方式允许这种转换,而 GCC 11.1(实现了 P2281R1)则拒绝它。因此,在 GCC 11.1 中,当is时,使用 ofstd::views::drop(i)始终是错误。isize_t

GCC 11.2 和 12 实现P2367R0 从第 24 条中删除列表初始化的误用,更改std::views::drop为使用括号而不是大括号,从而允许从size_t到差异类型的转换。