В чем разница между 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
выбирает свой следующий элемент с нетерпением, а не с ленивой. Эта разница, вероятно, не имеет значения большую часть времени, но может повлиять на критичный для производительности код.
Я не знаю, было ли это причиной этого изменения (я не читал статью).