Я написал следующую функцию для генерации пар из входных диапазонов.
template <typename Range>
requires std::ranges::borrowed_range<Range>
auto pair(Range && r)
{
// Creating a range of iterators to the original range
auto iterator_range = std::views::iota(
std::ranges::begin(r),
std::ranges::end(r));
return iterator_range
| std::views::drop(1)
| std::views::transform([](auto const & it){
using T = std::ranges::range_reference_t<Range>;
return std::pair<T,T>(*std::prev(it), *it);
});
}
Он специально имеет ограничение на входные данные заимствованного_диапазона, чтобы случайно не взять незаимствованные диапазоны и не получить висячие итераторы. Это отлично работает для ввода std::vector.
auto number_range = std::vector{0,1,2,3,4,5,6,7,8,9};
for(auto [a, b] : pair(number_range))
std::cout << a << ", " << b << std::endl;
Ссылка на вектор — это заимствованный диапазон. Однако я решил протестировать его, используя в качестве входных данных диапазон йот. Это тоже заимствованный диапазон, и поскольку типы, хранящиеся в паре, задаются с помощью using T = std::ranges::range_reference_t<Range>;
, я решил, что это просто сработает. Однако я был удивлен, когда приведенное ниже скомпилировано в бесконечный цикл.
auto number_range = std::views::iota(0, 10);
for(auto [a, b] : pair(number_range))
std::cout << a << ", " << b << std::endl;
Что здесь происходит? Версия gcc указана ниже и приводит к сбою
https://godbolt.org/z/q1EEaj6bW
Есть ли в моем коде какое-то неопределенное поведение? Версия clang приведена ниже и работает должным образом.
https://godbolt.org/z/c3fMqej31
Ожидаемые результаты
std::vector
0, 1
1, 2
2, 3
3, 4
4, 5
5, 6
6, 7
7, 8
8, 9
std::views::iota
0, 1
1, 2
2, 3
3, 4
4, 5
5, 6
6, 7
7, 8
8, 9
Я думаю, что представление йоты не может проверить свою верхнюю границу и так продолжается до тех пор, пока не достигнет целочисленного переполнения. Добавление флага sanitize проверяет наличие переполнения.
Да, я тоже так думаю, но удивительно, что та же библиотека работает, если использовать ее с clang.
Работает с clang и msvc. Что касается libstdc++, похоже, это проблема с интерфейсом GCC.
Ага. MSVC тоже работает. godbolt.org/z/nfdvb95Gr
Это разочаровывает. Обнаружил это, когда писал сообщение в внутреннем блоге компании о том, насколько аккуратны диапазоны в C++20 и как std::ranges::iota можно использовать для множества полезных вещей.
Небольшое изменение кода работает, но будет подвержено пограничным случаям в длине диапазона. godbolt.org/z/6zdhTv5xv
Интересно, что это работает godbolt.org/z/PxjesGhEe а это нет godbolt.org/z/816E85jxs
Ваш код — UB.
std::prev
работает только с Cpp17BidirectIterator, и хотя итератор удовлетворяет bidirectional_iterator
в случае iota_view
, он не соответствует требованиям Cpp17BidirectionIterator, поскольку его ссылка представляет собой pr-значение, поэтому это всего лишь входной итератор C++17. Применение std::prev
к итератору ввода C++17 является неопределённым поведением.
Использование ranges::prev
работает как положено для обоих компиляторов.
Так можно ли теперь всегда использовать std::ranges::prev? Или std::ranges::prev UB будет использоваться на старых итераторах типа?
@bradgonesurfing Для диапазонов C++20 всегда используйте ranges::prev
. ranges::prev
уже требует bidirectional_iterator
, поэтому старые итераторы типов не удовлетворяют ограничению и не будут компилироваться.
С помощью
-fsanitize=address,undefined
я получаю:runtime error: signed integer overflow: 2147483647 + 1 cannot be represented in type 'int'
- пока clang работает нормально (также используя libstdc++). Странный...