Как вы выполняете проверку границ с помощью стандартного диапазона?

std::vector и почти все другие контейнеры имеют очень удобный способ проверки границ: at(). У std::span этого явно нет.


person Ayxan Haqverdili    schedule 02.08.2020    source источник
comment
Зачем вам это в релизной сборке? Проверка границ является антишаблоном и имеет смысл только в контексте проверки ввода. В противном случае это усложняет поток управления под капотом, что препятствует существенной оптимизации. В первую очередь вы должны использовать интерфейсы итераторов. ИМХО, at() и подобные интерфейсы на основе исключений были заблуждением, которое служит в первую очередь для сокрытия логических ошибок при значительных накладных расходах.   -  person Ext3h    schedule 02.08.2020
comment
@Ext3h at() отлично подходит для отладки   -  person Ayxan Haqverdili    schedule 02.08.2020
comment
@Axyan для отладки у вас есть итераторы отладки (переключаемые соответствующими определениями) в большинстве реализаций. Но, как я уже сказал, обычно вы также никогда не захотите видеть их в рабочем коде.   -  person Ext3h    schedule 02.08.2020


Ответы (2)


Довольно коряво, но примерно так:

  1. используя положение
template<class Container>
auto& at(Container&& c, std::size_t pos){
    if(pos >= c.size())
        throw std::out_of_range("out of bounds");
    return c[pos];
}
  1. используя итераторы:
template<class Iterator, class Container>
auto& at(Container&& c, Iterator&& it){
    if(std::distance(c.begin(), it) >= c.size())
        throw std::out_of_range("out of bounds");
    return *it;
}
person Alberto Sinigaglia    schedule 02.08.2020
comment
Почему вы используете параметры rvalue-reference? делать семантику движения? - person JHBonarius; 02.08.2020
comment
@Ayxan Container&& должен быть прямой ссылкой, см. it-solves/3582313#3582313">здесь, поэтому вам не нужно переопределять его для каждого константного/неконстантного типа ecc ecc, на самом деле const std::vector<int> v{1,2,3}; std::cout << at(v, v.end()); должно работать нормально - person Alberto Sinigaglia; 02.08.2020
comment
@JHBonarius см. предыдущий комментарий - person Alberto Sinigaglia; 02.08.2020
comment
Я бы сказал, что эти подписи опасны. Ссылки переадресации могут привязываться к rvalue, а затем вы возвращаете ссылку (auto&) на что-то, что через мгновение исчезнет. - person Evg; 02.08.2020
comment
@Evg, так что вы говорите, что может быть лучше использовать ссылку на константу? - person Alberto Sinigaglia; 02.08.2020
comment
@Evg std::vector::operator[] делает то же самое. Нет? - person Ayxan Haqverdili; 02.08.2020
comment
@Evg Я действительно могу сделать что-то вроде этого std::cout << std::vector<std::string>{"a","b","c"}[2]; или std::vector<std::string>{"a","b","c"}.at(2), так что я думаю, что это не так уж важно - person Alberto Sinigaglia; 02.08.2020
comment
Я лишь выразил свою озабоченность. Возврат ссылки на rvalue может быть полезен, но подвержен ошибкам. Что, если вы вернете && для rvalue? Например, std::get: возвращает T&&, если задан кортеж rvalue, или delete перегрузка, которая принимает rvalue, например std::ref. - person Evg; 02.08.2020
comment
И почему именно std::distance(c.begin(), it) >= c.size(), а не it == c.end()? std::distance может занять линейное время, тогда как разыменование итератора — постоянное время. - person Evg; 02.08.2020
comment
@Evg, потому что, если вы пройдете c.end()+10, с == вы не получите реальную проверку привязки - person Alberto Sinigaglia; 02.08.2020
comment
c.end() + 10 - это всегда неопределенное поведение, даже для std::vector, и то, что происходит после UB, не имеет значения. - person Evg; 02.08.2020
comment
@Evg Ayxan сказал, что будет использовать этот код для отладки, и поэтому почему мы должны заботиться об этом случае UB, когда мы можем легко его избежать? PS: at() получит size_t, поэтому, вероятно, версия с итерацией не будет использоваться. - person Alberto Sinigaglia; 02.08.2020
comment
@Evg также span содержит заразный контейнер памяти, который будет иметь постоянную оценку расстояния, поскольку вы, вероятно, можете просто использовать operator- между двумя итераторами. - person Alberto Sinigaglia; 02.08.2020
comment
@Ayxan, это так. Но бесплатная функция std::get() делает нечто иное. Разве бесплатный at() не должен делать то же самое? Я просто спрашиваю. - person Evg; 02.08.2020
comment
operator- выглядит как хорошая альтернатива. Тогда этот (общий) код не будет компилироваться для итераторов, которые не поддерживают постоянное время std::distance, и это хорошо. - person Evg; 02.08.2020
comment
@Evg Я согласен с обоими вашими замечаниями по поводу ссылки на rval и operator-. Однако, поскольку это предназначено только для отладки, это не должно иметь значения в любом случае. - person Ayxan Haqverdili; 02.08.2020
comment
@Ayxan Кстати, GCC с -D_GLIBCXX_DEBUG сообщает о нарушении утверждения для доступа за пределы [] на std::span: Assertion '__idx < size()' failed.. - person Evg; 02.08.2020

статья, которая представила span в стандартная библиотека говорит:

Проверка диапазона и безопасность границ

Все обращения к данным, инкапсулированным в диапазоне, концептуально проверяются по диапазону, чтобы гарантировать, что они остаются в пределах диапазона. То, что на самом деле происходит в результате несоблюдения ограничений boundssafety во время выполнения, — это неопределенное поведение.

То есть операции имеют узкие контракты, дающие свободу исполнителю.

Если ваша стандартная библиотека не позволяет вам контролировать поведение с соответствующей степенью детализации. gsl-lite предлагает замену с настраиваемым поведением при нарушении контракта. Microsoft GSL ранее можно было настроить, но теперь всегда прекращается при нарушении контракта, обсуждалось здесь (что может быть именно тем, что вам нужно).

person ecatmur    schedule 02.08.2020
comment
Если ваша стандартная библиотека не позволяет вам контролировать поведение с соответствующей степенью детализации, что это значит? Я хочу написать переносимый код, если это возможно. Это часть стандартного интерфейса или нет? - person Ayxan Haqverdili; 02.08.2020
comment
@Ayxan Portable, так как вы будете использовать его с несколькими реализациями, или переносимый, как с открытым исходным кодом? В настоящее время произвольная реализация вполне может обойтись без каких-либо проверок, хотя это может измениться в будущем, если контракты когда-либо появятся. - person ecatmur; 02.08.2020
comment
Портативный, например, если я использую std::vector::at, я знаю, что он будет проверять границы, использую ли я GCC, Clang, MSVC или что-то еще. - person Ayxan Haqverdili; 02.08.2020
comment
@Ayxan, вы можете проверить это, прочитав документацию, исходный код и/или протестировав. В стандарте нет никаких гарантий - поведение при нарушении границ указано как неопределенное. - person ecatmur; 02.08.2020
comment
@ayxan, который имеет определенное поведение на всех входах, называется широким контрактом. Тот, который этого не делает, известен как узкий контракт. В этом ответе говорится, что интерфейс, являющийся узким контрактом, был преднамеренным. Узкие контракты разрешают проверку границ, но не предписывают ее. - person Yakk - Adam Nevraumont; 02.08.2020
comment
@Yakk-AdamNevraumont Я думаю, что это всегда имело место для operator[], и в качестве альтернативы у нас была отдельная функция более широкого контракта (at), и мне было интересно, почему это не было добавлено в span. - person Ayxan Haqverdili; 02.08.2020
comment
@ayxan Я не знаю, рассматривался ли в какой-либо момент доступ к широкому контракту. Широкие контракты обычно обесцениваются для стандартной библиотеки; см. open-std.org/jtc1/sc22 /wg21/docs/papers/2019/p1743r0.pdf - person ecatmur; 02.08.2020
comment
@ecatmur Я хотел сказать, что в старые добрые времена у нас было at для наших контейнеров, таких как vector и basic_string - person Ayxan Haqverdili; 02.08.2020
comment
@ecatmur, хотя в документе объясняется, почему такой широкий контракт вреден для производственного кода, я хотел использовать его для целей отладки. Я ценю ссылку, хотя, спасибо. - person Ayxan Haqverdili; 02.08.2020
comment
@AyxanHaqverdili at выдает исключение, когда индекс выходит за границы. Выход за границы в span рассматривается как нарушение контракта, и поведение не контролируется в коде, поскольку вы можете настроить отсутствие действий, создание исключения или завершение. - person Yongwei Wu; 08.03.2021