В чем разница между split_view и lazy_split_view в C ++?

Я прочитал последний черновик, куда добавлен.

Но позже я понял, что его переименовали в lazy_split_view, а split_view был обновлен.

libstdc++ также недавно реализовал это с помощью GCC Trunkверсия https://godbolt.org/z/9qG5T9n5h

У меня есть простая наивная программа, которая показывает использование двух представлений, но я не вижу их различий:

      #include <iostream>
#include <ranges>

int main(){

    std::string str { "one two three  four" };

    for (auto word : str | std::views::split(' ')) {
        for (char ch : word)
            std::cout << ch;
        std::cout << '.';
    }

    std::cout << '\n';

    for (auto word : str | std::views::lazy_split(' ')) {
        for (char ch : word)
            std::cout << ch;
        std::cout << '.';
    }

}

Выход:

      one.two.three..four.
one.two.three..four.

пока я не заметил различий при использовании в качестве std::span<const char> для обоих представлений.

В первом: std::views::split:

      for (std::span<const char> word : str | std::views::split(' '))

компилятор принимает мой код.

А во втором: std::views::lazy_split

      for (std::span<const char> word : str | std::views::lazy_split(' ')) 

выдает ошибки компиляции.

Я знаю, что между этими двумя есть различия, но мне нелегко их заметить. Это отчет о дефекте в C++20 или новая функция в C++23 (с изменениями), или и то, и другое?

1 ответ

Решение

Я просмотрел соответствующий документ (P2210R2 от Барри Ревзина) и был переименован в lazy_split_view. Новый split_view отличается тем, что предоставляет вам другой тип результата, который сохраняет категорию исходного диапазона.

Например, наша строка является непрерывным диапазоном, поэтому в результате будет получен непрерывный поддиапазон. Раньше он давал вам только прямой диапазон. Это может быть плохо, если вы попытаетесь выполнить многопроходные операции или получить адрес в базовом хранилище.

Из примера статьи:

      std::string str = "1.2.3.4";
auto ints = str 
    | std::views::split('.')
    | std::views::transform([](auto v){
        int i = 0;
        std::from_chars(v.data(), v.data() + v.size(), i);
        return i;
    });

будет работать сейчас, но

      std::string str = "1.2.3.4";
auto ints = str 
    | std::views::lazy_split('.')
    | std::views::transform([](auto v){
        int i = 0;
        // v.data() doesn't exist
        std::from_chars(v.data(), v.data() + v.size(), i);
        return i;
    });

не будет, потому что диапазон v это только прямой диапазон, который не обеспечивает data() член.

Origianl Ответ

У меня создалось впечатление, что он тоже должен быть ленивым (в конце концов, лень была одним из преимуществ предложения диапазонов), поэтому я провел небольшой эксперимент :

      struct CallCount{
    int i = 0;

    auto operator()(auto c) {
        i++;
        return c;
    }

    ~CallCount(){
        if (i > 0) // there are a lot of copies made when the range is constructed
            std::cout << "number of calls: " << i << "\n";
    }
};


int main() {
    
    std::string str = "1 3 5 7 9 1";

    std::cout << "split_view:\n";

    for (auto word : str | std::views::transform(CallCount{}) | std::views::split(' ') | std::views::take(2)) {
    }

    std::cout << "lazy_split_view:\n";

    for (auto word : str | std::views::transform(CallCount{}) | std::views::lazy_split(' ') | std::views::take(2)) {
    }    
}

Этот код печатает (обратите внимание, что оператор работает с каждым символом в строке):

      split_view:
number of calls: 6
lazy_split_view:
number of calls: 4

Так что же происходит?

Действительно, оба взгляда ленивы. Но есть отличия в их лени. В transformто, что я поставил перед ним, просто подсчитывает, сколько раз он был вызван. Оказывается, вычисляет следующий элемент с нетерпением, а while останавливается, как только попадает в пробел после текущего элемента.

Вы можете видеть, что строка состоит из чисел, которые также отмечают свой индекс символа (начиная с 1). В take(2) должен остановить цикл после того, как мы увидим "3" в str. И действительно lazy_split останавливается на пробеле после «3», но останавливается на пробеле после «5».

По сути, это означает, что splitвыбирает свой следующий элемент с нетерпением, а не с ленивой. Эта разница, вероятно, не имеет значения большую часть времени, но может повлиять на критичный для производительности код.

Я не знаю, было ли это причиной этого изменения (я не читал статью).

Другие вопросы по тегам