Здравствуйте, sergii.p, Вы писали:
SP>Здравствуйте, Sm0ke, Вы писали:
S>>Завести тип индекса с диапазоном, который в конструкторе проверяет выход за границы.
S>>Тогда обращение к элементу по этому индексу будет boundary safe без дополнительных проверок.
SP>это неочевидно:
SP>SP>std::vector<int> mut_v = {1, 2, 3};
SP>const auto correct_idx = calc_index(mut_v, 0);
SP>mut_v.clear();
SP>std::cout << mut_v[correct_idx];
SP>
Дак со стандартным вектором такое не прокатит.
Тут нужен custom fixed sized контейнер.
SP>чтобы это работало, получается надо иметь в языке что-то похожее на borrow checker. Вот в Rust имеет смысл "поесть этот кактус".
Про borrowed checker немного не понял...
SP>Вообще идея Ada на диапазонные значения выглядит немного нежизнеспособно. Звучит круто, а на практике бонусов не видно.
не знаю, не пробовал. может быть вы правы.
Здравствуйте, Sm0ke, Вы писали:
S>Поясните пожалуйста подробнее
S>Какой ордеринг там может понадобиться?
partial_ordering
Топологическая сортировка
Применение
При помощи топологической сортировки строится корректная последовательность выполнения действий, всякое из которых может зависеть от другого: последовательность прохождения учебных курсов студентами, установки программ при помощи пакетного менеджера, сборки исходных текстов программ при помощи Makefile'ов.
Ну например, без математики тяжело изучать физику и химию. Значит между Математика <=> Физика = less и Математика <=> Химия = less. А вот физику и химию можно изучать параллельно: Физика <=> Химия = unordered. На этом основании можно последовательность обучения построить как (Математика Физика Химия ) так и (Математика Химия Физика ).
Здравствуйте, Sm0ke, Вы писали:
S>Про borrowed checker немного не понял...
в том смысле, что borrow checker приведённые выше ошибочные конструкции не даст создать
fn main() {
let mut container = MyContainer::new().push(1).push(2).push(3);
match try_read_2nd(&container) {
Ok(v) => println!("Ok {}", v),
Err(e) => println!("Err {} not in 0..{}", e.incorrect_idx, e.max)
}
let incorrect_idx = container.create_index(1).unwrap(); // создать можем без проблем
container.clear(); // а дальше уже ошибка компилятора cannot borrow `container` as mutable because it is also borrowed as immutable
println!("Ok {}", container[incorrect_idx]);
}
fn try_read_2nd(container: &MyContainer) -> Result<u8, BoundError> {
let idx = container.create_index(1)?;
Ok(container[idx]) // а здесь всё нормально: ссылаемся на константный контейнер. Он поменять размер не может. Можем не проверять на выход за границы.
}
| реализация |
| use std::vec::Vec;
use core::ops::Index;
#[derive(Debug)]
struct BoundError {
incorrect_idx: usize,
max: usize
}
struct MyIndex<'a> {
i: usize,
#[warn(dead_code)]
parent: &'a MyContainer,
}
struct MyContainer {
inner: Vec<u8>,
}
impl MyContainer {
fn new() -> MyContainer {
MyContainer { inner: Vec::new() }
}
fn push(self, v: u8) -> Self {
let mut temp = self;
temp.inner.push(v);
temp
}
fn clear(&mut self) {
self.inner.clear();
}
fn create_index(&self, v: usize) -> Result<MyIndex, BoundError> {
if self.inner.len() == 0{
return Err(BoundError{incorrect_idx: v, max: 0})
}
let size = self.inner.len() - 1;
match v {
i if (0..=size).contains(&i) => Ok(MyIndex { i: v, parent: &self }),
_ => Err(BoundError{incorrect_idx: v, max: self.inner.len()})
}
}
}
impl<'a> Index<MyIndex<'a>> for MyContainer {
type Output = u8;
fn index(&self, index: MyIndex<'a>) -> &Self::Output {
self.inner.index(index.i)
}
}
|
| |
Возвращать из оператора <=> свой тип, и при этом иметь = default позволяет пока лишь clang ...
Оборачиать enum в класс, чтобы добавить туда конструкторы из разных ордериногов
// std::strong_ordering, std::weak_ordering, std::partial_ordering
Мне не очень понравилось. Оператор static_cast перегрузить пока мы тоже не можем.
Захотелось чего-то такого:
void test_switch()
{
switch( 1 <=> 0 > as_compare )
{
case t_compare::n_less :
std::cout << "less\n"; break;
case t_compare::n_equal :
std::cout << "equal\n"; break;
case t_compare::n_greater :
std::cout << "greater\n"; break;
}
}
И у меня получилось это осуществить.
Пришлось завести структуру преобразования.
template <typename Result>
struct t_cast;
Которую можно будет специализировать для своих типов (или любых других)
Например для енума результатов сравнения от всех трёх ордерингов
enum class t_compare
{
n_none,
n_less,
n_equal_partial,
n_equal_weak,
n_equal, // strong
n_greater,
n_unordered
};
Делаю специализацию у t_cast (с перегруженным оператором круглые скобки)
template <>
struct t_cast<t_compare>
{
using result_type = t_compare;
constexpr result_type
operator () (std::strong_ordering p) const
{
return
p < 0 ? result_type::n_less :
p > 0 ? result_type::n_greater : result_type::n_equal;
}
constexpr result_type
operator () (std::weak_ordering p) const
{
return
p < 0 ? result_type::n_less :
p > 0 ? result_type::n_greater : result_type::n_equal_weak;
}
constexpr result_type
operator () (std::partial_ordering p) const
{
return
p < 0 ? result_type::n_less :
p > 0 ? result_type::n_greater :
p == 0 ? result_type::n_equal_partial : result_type::n_unordered;
}
};
Для вызова этой системы преобразования определил глобальные переменные as_type, as_compare
// global template object
template <typename To>
constexpr inline t_cast<To>
as_type{};
// global object
constexpr inline t_cast<t_compare>
as_compare{};
И добавил перегрузку оператора
> (в правой части t_cast, в левой — любой тип)
Чтобы можно было использовать не только как
as_type<t_compare>(var)
Но и
var > as_type<t_compare>
template <typename From, typename Result>
constexpr Result
operator > (From p_from, const t_cast<Result> p_cast)
{ return p_cast(p_from); } // вызов оператора круглые скобочки
Проверка, что это работает:
void test_switch()
{
switch( 1 <=> 0 > as_compare )
{
case t_compare::n_less :
std::cout << "less\n"; break;
case t_compare::n_equal :
std::cout << "equal\n"; break;
case t_compare::n_greater :
std::cout << "greater\n"; break;
}
}
int main()
{
t_compare v1{1.0/0.0 <=> 0.0/0.0 > as_type<t_compare>};
t_compare v2{1.0/0.0 <=> 0.0/0.0 > as_compare};
std::cout
<< (v1 == t_compare::n_unordered) << ' '
<< (v1 == v2) << ' '
<< (as_compare(1.0/0.0 <=> 0.0/0.0) == t_compare::n_unordered) << '\n';
test_switch();
}
Весь код:
готболт
Результат:
1 1 1
greater
Program returned: 0
Понимаю, что выбор оператора > для t_cast может быть под вопросом. Тем более что такая перегрузка возвращает не тип bool
Заглянув в доку
cpp ref precedence
Видно что оператор
> имеет меньший приоритет, чем
<=>
Можно было бы взять bitwise оператор
| вместо этого
p/s: Получилась настраиваемая система преобразований для любых типов. Которую можно использовать независимо от стандартного продвижения типов языка CPP и штук из стандартной библиотеки.
Хранить ли все ордеринги в одном енуме тоже вопрос.