Функция std::char_traits::copy
реализована в libc++
следующим образом:
template <class _CharT>
inline _LIBCPP_CONSTEXPR_AFTER_CXX17
_CharT*
char_traits<_CharT>::copy(char_type* __s1, const char_type* __s2, size_t __n)
{
if (!__libcpp_is_constant_evaluated()) {
_LIBCPP_ASSERT(__s2 < __s1 || __s2 >= __s1+__n, "char_traits::copy overlapped range");
}
char_type* __r = __s1;
for (; __n; --__n, ++__s1, ++__s2)
assign(*__s1, *__s2);
return __r;
}
Условие в _LIBCPP_ASSERT
кажется обратным. Разве это не должно быть
__s1 < __s2 || __s1 >= __s2+__n
Что мне не хватает?
Диапазоны не могут перекрываться с обоих концов в соответствии с требованиями в eel.is/c++draft/char.traits.require#tab:char.traits.req. Поэтому я считаю, что следует проверять оба направления.
Это не ошибка, условие libc++ до тех пор, пока C++23 не станет правильным. std::char_traits::copy:
Поведение не определено, если скопированные диапазоны символов перекрываются, т. е.
src
находится в[dest, dest + count)
.
Плохо: (src >= dest && src < dest + count)
Утверждение: !(src >= dest && src < dest + count)
-> !(src >= dest) || !(src < dest + count)
-> (src < dest) || (src >= dest + count)
-> __s2 < __s1 || __s2 >= __s1 + __n
.
Здесь применяется правило математической логики: !(a && b) равно !a || !б.
«Поведение не определено, если скопированные диапазоны символов перекрываются, т. Е. src находится в [dest, dest + count)». Это ошибка в cppreference. «Перекрытие диапазонов» не эквивалентно «src находится в [dest, dest + count)». В стандарте указано правильное условие.
Возможно недоработка стандарта и исправлена в черновике. Какие исходники libc++ вы просматриваете?
@heapunderrun Это не стандарт, это общедоступный черновик. И это утверждение выходит за рамки стандарта, LLVM просто сделал его для дополнительной диагностики. GNU libstdc++ не имеет утверждений: gcc.gnu.org/onlinedocs/libstdc++/libstdc++-api-4.5/…
Вы правы, похоже на ошибку в С++ 11-20, исправленную в проекте С++ 23.
@н.м. Хорошая точка зрения! Я только что посмотрел на фактические исходники LVVM, этот код в вопросе устарел: _LIBCPP_DEPRECATED_("char_traits<T> for T not equal to char, wchar_t, char8_t, char16_t or char32_t is non-standard and is provided for a temporary period. It will be removed in LLVM 18, so please migrate off of it.")
. Фактический код использует std::copy_n()
.
До разрешения проблемы LWG 3085 (нацеленной на C++23) проверка, выполненная в показанном коде, фактически является единственным предварительным условием, указанным для char_traits::copy
. Начало исходного диапазона может не лежать в целевом диапазоне, но начало целевого диапазона может находиться в исходном диапазоне.
Однако тогда реализация в показанном коде будет неправильной. Копирование от начала до конца в цикле может привести к преждевременной перезаписи исходных элементов.
Решение проблемы LWG делает предварительное условие более строгим, требуя, чтобы исходный и целевой диапазоны вообще не перекрывались. По-видимому, несколько реализаций не учитывали правильно ранее разрешенное перекрытие в своей реализации.
Это ошибка в стандарте C++. Libc++ просто точно воспроизводит его.
Версии от C++11 до C++20 перечисляют бессмысленное требование, чтобы источник не находился в диапазоне назначения.
X::copy(s,p,n) ... Preconditions: p not in [s,s+n)
Намерение состоит и всегда заключалось в том, чтобы исходный диапазон и диапазон назначения не перекрывались. Это исправлено в черновике C++23:
X::copy(s,p,n) ... Preconditions: The ranges [p, p+n) and [s, s+n) do not overlap.
Это не совсем верно. Если бы предполагалось, что текст должен соответствовать формулировке, им нужно было бы сделать копию задом наперед, чтобы избежать допустимого дублирования. Так что я все еще думаю, что выбор, который они сделали, немного странный, если он был преднамеренным.
@ 273K это на самом деле похоже на
memcpy
en.cppreference.com/w/cpp/string/char_traits/copy