Обновить
0

Студент/ Геймдизайнер

Отправить сообщение

"Максимально" это конечно перегиб, всё же malloc гарантированно выровнен по выравниванию наиболее выровненного встроенного сишного типа (обычно выровнен в два машинных слова), чего просто атомикам, конечно, и хватит, но всё же для гарантий в более общем случае лучше использовать aligned_alloc, например при выравнивании по ширине кеш-линии, как иногда делается

Никакого нюанса нет, можно ограничение возможных значений считать просто инвариантом над u8, да и если захочется, всё ещё есть способы упаковать под ситуацию.

в расте это как будто окончательно решили тем, что запретили = возвращать что либо кроме (). как по мне, само то, что присваивание возвращает ссылку является стрельбой в ногу, как и возможность типам быть конвертируемыми в bool кроме пары случаев когда тип действительно близок по смыслу навроде bool? в C# ну или, может быть, любых option-типов

я не говорил о какой-то пользе трюка. это и есть просто цикл, записанный через рекурсию

собственно, суть лишь в том, что всё можно записать через циклы и мутабельные переменные, можно аналогично реализовать с неизменяемыми переменными и хвостовой рекурсией — вопрос лишь в тяжести (да и в любом случае, компилятор это обратно соптимизирует в обычный цикл)

не буду говорить, что в этом есть хоть какая-то польза с точки зрения исполнения кода, но как нечто на подумать — это может быть забавным.

да и просто то, что рекурсия написана нехвостовая, когда её почти тривиально можно сделать таковой, всё равно кажется не самой приятной вещью

Это другой алгоритм.

Нет, это то же самое

я её уже однажды придумав написал, и потому легко повторю ещё раз :)

uint64_t fib(uint64_t n, uint64_t curr = 0, uint64_t prev = 1) {
  if (n == 0) return curr;
  return fib (n-1, curr + prev, curr);
}

можно даже легко переписать для случаев, когда n < 0

по сути, все аргументы это просто всё те же мутабельные переменные, если бы мы писали через обычные циклы

немного странно, учитывая, что что факториал, что возведение в степень легко переписать через хвостовую рекурсию (хоть, конечно, и потребуется от компилятора её поддержка — но на gcc или clang это просто один флаг поставить)

далее планируемые числа фибоначи тоже

хотя вот числа аккермана и определитель без введения какого-то списка на куче кажутся тяжёлыми в этом плане (хотя я никогда не писал реализацию для аккермана хоть какую, так что знать не могу… вообще, это наверное просто вопрос того, чтоб немного поразмыслить)

в любом случае, для восстановления справедливости, не было бы неинтересным видеть тоже самое что писалось через "адекватные" циклы, написанным через tailrec

(впрочем, всё же очевидным минусом тут является в любом случае, что компилятор волен поступить как захочет)

мне до сих пор была интересна эта серия статей, так что я бы подождал одну про синхронизации, спасибо.

кроме раста я наверное не знаю языков, в которых к синхронизации как-то принуждают (но я не особо и разбирался в теме), так что также будут интересны и сравнения (в расте же вроде есть трейты Sync и Send, которые мешают передавать значения нарушающие синхронизацию в другой поток — даже если разымплементировать их надо вручную для чего-то самописного; и чтобы взять ссылку из значения под мьютексом, нужно его залочить, если память не изменяет)

в той же java хочешь пиши synchronized-блоки, хочешь нет, вроде как, а плюсы как всегда позволяют тебе делать что хочешь, но по итогу слишком уж на отвали. (но опять же, я не очень прямо хорошо погружён, я лишь студент, который многопоток использовал лишь пару раз, так что могу ошибаться)

----

по поводу конструкторов:

в расте у полей значения по умолчанию запрещены, и каждое поле нужно обязательно инициализировать; чтобы же получить значение по умолчанию нужно использовать Default::default() или какой-нибудь _::new(), а чтобы проинициализировать напрямую нужноуказать значение для кпждого поля по отдельности.

противоположным является подход например в C, Java или C#, где если не указать значение по умолчанию, то выберется конструктор по умолчанию, null или т.п. Конечно, в C# есть required, который правда всё равно для части случаев будет так себе, а в плюсах можно написать какой-то собственный простенький тип, в котором будет что-то вроде

template<class T>
operator T() const
{
  static_assert(false);
}

, но как будто это костыли, о которых разработчики вряд ли будут думать (а может и будут, я не очень знаю).

подход аргентума я вижу похожим, но хоть немного лучшим в том, что значение по умолчанию нужно выбрать самим.

заодно синтаксис value.{/*lambda*/} мне напоминает что-то похожее в сишарпе или джаве (хотя наверное в котлине оно и вовсе будет околоидиоматичным, хотя и не уверен):

new SomeType() {{
  some_field = ...;
  ...
}}
some_value with {some_field = ..., ...}
some_value.apply{
  some_field = ...
}

some_value.also{
  it.some_field = ...
}

впринципе, в C# (и Rust тоже вроде как) также можно и сделать extension-метод, чтобы работал так же как also в котлине, но всё равно котлин в этом плане как будто красивейший, и подход из аргентума мне напоминает его, тем более что это ещё и идёт по умолчанию — по дизайну решение безусловно красивое.

ну а так, мне тоже нравится будто бы повсеместный отказ от конструкторов (и от перегрузок функций в частности)

просто в аргентуме значение по умолчанию будто бы словно есть абсолютно всегда, если я правильно понимаю, что не во всех ситуациях полезно (условно не существует же никакого сокета или дескриптора файла по умолчанию — верно? а если и есть, то стоит считать, что он должен вести себя равно какому-нибудь null, не так ли?)

по поводу классов: от того, что всегда есть значения по умолчанию, всё хорошо будет, не в этой модели, а в общем случае? и не будет ошибок с тем, что потенциально можно пропустить инициализацию какого-то поля с данной моделью их инициализации? хотя понимаю, что более старые языки тоже многие к этому склонны, но всё-таки.

и если тут утверждается, что тут счёт ссылок неатомарный, то что будет в многопотоке?

есть ли какая-то гарантия что я не протащу слабую ссылку в другой поток имея возможность редактировать основную из этого?

последний абзац я уже не понимаю, ибо так всей этой теорией не интересовался, но я просто думаю, что для N-мерного пространства можно определить N-мерный тензор, который как бы будет результатом перемножения 0 элементов, и затем уже получать то что нужно как было сказано через тензорное произведение на векторы-множители

чисто технически, есть ведь ещё и типы-степени (функции)… сосредотачиваться лишь на суммах и произведениях — не так хорошо, я полагаю, но как бы с другой стороны, функции должны быть даже в тех япах, где нету структур… немного спускаясь в полуфилософскую шутливость, операции открылись словно наоборот от классической алгебры. если конечно не вспоминать о first class – функциях и замыканиях…

В этом есть какие-то минусы кроме нарушения около-стереотипа о том, что раз умножение, то обязательно именно двух элементов?

К тому же такое умножение 4 векторов всё равно работало бы также как и для 3д-умножения — результатом будет пятый, вектор перпендикулярный этим четырём, при том что его длина будет соответвовать площади гиперпараллелепипеда между ними. Как минимум, поиск перпендикулярного вектора это задача очень часто реально полезная.

да и если и рассматривать только пространства где произведения могут быть только от двух элементов, с этим же вопросом было бы интересно подойти по поводу того, как понять для ккких пространств определено произведение от n элементов для произвольного неотрицательного целого n. если условно произведение от 1 элемента определено для каждого пространства, от 2 лишь для 0, 1, 3, 7, от 0 при беглом обдумывании непонятно как определять кроме как для 1- и 0-мерного пространства — и вот это типа обобщить для произвольного фиксированного числа множителей.

знаю это, просто посчитал кодпоинты за символы

так-то да, просто я посчитал управляющие символы так же за символы.

но если делать именно так, то для условного о̴̷̰͙͓̰ͫ͗̉͜ͅ никаких 32 бит не хватит.

при том, для часто используемых сочетаний символ-лигатура (ё, だ, ã, …) и так есть отдельные кодпоинты. отдельно взятые лигатуры ведь нужны именно когда нужно олигатурить произвольный символ, нет? (хотя отдельно взятого э́ какого-нибудь не хватает или отдельных кодпоинтов для разных вариаций одного символа (привет, cjk) — но тут скорее вопрос к создателям юникода)

UTF-32 разве как раз этого не делает?

[[maybe_unused]] добавили в C++17. Даже до этого, если нет согласия с линтером и есть достаточная уверенность, что он брешет, вариант с (void) или любой другой подобной глушилкой был бы не так плох.

Собственно, я впринципе с точки зрения линтера не особо понимаю, в чём проблема, если переменная не использовалась в одной из веток — важнее же чтобы использовалась хотя бы в одной, разве нет?

Я не особо математик и могу чего-то не знать, поэтому скажу лишь свои мысли как обывателя.

Как будто векторное произведение определёно на любой мерности, просто придётся работать с тензорами, а не векторами.

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

Но вообще, если не лезть в тензоры, можно просто сказать, что век. произведение в ланной мерности N определено для N - 1 векторов.

Способ считать век. произведение как детерминант матрицы, где самая верхняя строчка это единичные векторы очень легко обобщается до многомерных пространств. Я этим в школе для своих развлечений вроде как типа и пользовался.

это не так? в 4-мерном пространстве двум векторам может быть перпендикулярна плоскость — не значит ли это, что любой вектор между двумя точками этой плоскости будет перпендикулярен изначальному вектору?

jit-компилятор мб смог бы, а вот aot только если в несколько проходов. ну или сделать какую-то статическую переменную на случай "ладно, не хочу". но не думаю, что aot-компилятороделы будут использовать такой динамический подход.

Может быть, всё же дело в том, что я не джавист и джавовские практики мне не знакомы. Если так рассудить, явные исключения действительно схожи с Result, а неявные с panic. Но, в какой-то мере, мне кажется, что проблема исключений в некоторой степени и в том, что ты с ними работать будешь как с исключениями. Возможно, было бы удобно всё же сделать какой-то Result типа такого, чисто чтобы что-то делать локально внутри функции и локально с результатом конкретной функции — хотя это, наверное, и грозит потерей стектрейса (или нет, я не помню, как это работает).


public sealed interface Result<T, E extends Exception> {
    record Ok<T, E extends Exception>(T value) implements Result<T, E> {
        public T unwrap(){
            return value;
        }

        public T rethrow() {
            return value;
        }

        public <T0> Ok<T0, E> map(Function<T, T0> fn) {
            return new Ok<>(fn.apply(value));
        }

        //and other
    }
    record Err<T, E extends Exception>(@NotNull E err) implements Result<T, E> {
        public T unwrap(){
            throw new RuntimeException(err);
        }

        public T rethrow() throws E {
            throw err;
        }

        @SuppressWarnings("unchecked")
        public <T0> Err<T0, E> map(Function<T, T0> fn) {
            return (Err<T0, E>)this;
        }

        //and other
    }
    @FunctionalInterface
    interface ThrowingFn<T, E extends Exception>{
        T run() throws E;
    }

    @SuppressWarnings("unchecked")
    @NotNull
    static<T, E extends Exception> Result<T, E> catch_result(ThrowingFn<T, E> fn) {
        try {
            return new Ok<>(fn.run());
        } catch (Exception e) {
            return new Err<>((E)e);
        }
    }

    static<T> T unwrap_result(ThrowingFn<T, ? extends Exception> fn) {
        try {
            return fn.run();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    T unwrap();
    T rethrow() throws E;
    <T0> Result<T0, E> map(Function<T, T0> fn);

    //and other
}

Во всяком случае, я думаю, что есть пару вещей, которые изменить бы хотелось, чтобы всё работало приятнее:

  • Синтаксис

    Условный try{ } catch(ExceptionType e) {} лично для меня имеет пару проблем в дизайне:

    1) Фигурные скобки вокруг try и catch обязательные — рассчитаны лишь на то, чтобы оборачивать всю функцию — при необязательности для остальных конструкций.

    2) Тип исключения — если throws функции лишь одного типа, тип в catch можно бы и вывести из контекста

    3) Блок try всегда возвращает void — нужно заводить переменную вне блока для результата.

    4) Сахар на try{ } catch(ExceptionType e) {throw new RuntimeException(e)} всё же был бы приятен

  • Неявность и схожесть с неявными исключениями — поставь throws Exception на функцию — и не заметь throw там, где ты уже хочешь его поймать.

  • Не помешали бы существующие отдельно методы, кидающие явные и неявные исключение в тех случаях, когда принято кидать лишь неявные.

  • Неудобность с точки зрения типизации. Опять же, условный Function<A, B> уже запрещает кидать явные эксепшены. Если бы явные исключения становились частью возвращаемого типа, было бы удобнее. Хотя и можно сделать условный ThrowingFunction<ArgT, ResT, ExT extends Exception>, навроде того, что я написал сверху, но так-то большинству библиотек и фреймворков всё же будет всё равно.

Но может быть и такое, что это всё вообще не нужно, всё и так работает замечательно, я не прав и у меня мозг просто немного оrustенел, так сказать.

Если проблема в невидимых загрузках, то можно с C++23 вот так сделать:

void DoThing(this X& self) {
    for (int64_t k = 0; k < self->Size; k++) {
        FuncThatIsNotInlined(k);
    }
}

И вызывать как обычный метод.

Информация

В рейтинге
6 030-й
Откуда
Беларусь
Дата рождения
Зарегистрирован
Активность

Специализация

Разработчик игр
C++ stl
C#
Visual Studio