Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Bug or compilation error with some compilers for simple std::ranges code

I have a piece of code that uses the ranges library of C++20, taken from this SO anwer. The code is rejected by some compiler (versions) and some older GCC versions return garbage. Which compiler is right?

The code is supposed to print the elements of the first column in 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;
}

Compiler Explorer

Output:

With GCC 10.1, 10.2, 10.3, 10.4:

b

Doesn't compile with GCC 11.1 and 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>')

Expected

a
bb
cc

Works as expected with GCC 11.2, 11.3, 12.1, 12.2, MSVC 19.33

like image 342
joergbrech Avatar asked Apr 06 '26 15:04

joergbrech


1 Answers

GCC 11.2, 11.3, 12.1, 12.2, MSVC 19.33 are correct.

Clang up to 15 does not support libstdc++'s <ranges> at all, and with libc++ the program works correctly.

GCC 10.1, 10.2, 10.3, 10.4 and 11.1 mishandle std::views::drop(i) which is used in ith_element(i).

Here's why std::views::drop(i) is complicated, and how old GCC did it wrong:

How the argument is stored

To make range | std::views::drop(i) work, the result of std::views::drop(i) must memorize the value of i. But should it store a copy of i, or a reference to i?

GCC 10.x stores a reference when the argument is an lvalue. Hence, with GCC 10.x, the result returned by return std::views::drop(i) | std::views::take(1); contains a dangling reference.

GCC 11.1 implements P2281R1 Clarifying range adaptor objects which makes such objects always store a copy of the argument.

Brace vs. paren initialization

Does range | std::views::drop(i) initialize the result with braces (std::ranges::drop_view{range, i}) or with parentheses (std::ranges::drop_view(range, i))?

The relevant constructor of drop_view is drop_view(V, range_difference_t<V>). Note that the second parameter is a signed integer type, and list initialization disallows unsigned to signed conversion (because it's a narrowing conversion). Thus, with braces, the corresponding argument cannot be an unsigned integer (e.g. size_t).

GCC 10.x somehow permits this conversion, while GCC 11.1 (with P2281R1 implemented) rejects it. Hence, with GCC 11.1, uses of std::views::drop(i) is always an error when i is size_t.

GCC 11.2 and 12 implement P2367R0 Remove misuses of list-initialization from Clause 24, which changes std::views::drop to use parentheses instead of braces, and thus allows conversion from size_t to the difference type.

like image 103
cpplearner Avatar answered Apr 08 '26 04:04

cpplearner



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!