Как стать автором
Обновить

Комментарии 34

Динамическая типизация своими руками при помощи жевачки, синей изоленты и какой-то матери.

В том-то и дело, что в C++ вместо динамической типизации при любой возможности стараются использовать статическую. И на этом поле язык действительно очень хорош.

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

Я бы немало прифигел, изучая программирование, если бы узнал, что в прекрасном будущем 21 веке в популярном языке будут предусматривать специальные конструкции, чтобы соблюсти строгую типизацию в коде, который не только никогда не выполняется, но и не должен компилироваться в несущие содержание инструкции. С практической точки зрения, это (типизация в отбрасываемом коде) выглядит как сумерки рассудка. Механизм затмил собой своё назначение.

Не совсем понял, что Вы имеете в виду.


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

Мне не нравится конкретно вот это вот:

В конструкции if constexpr constexpr выражение вычисляется во время компиляции, после чего код в одной из ветвей отбрасывается.

Но важно отметить, что отброшенный код все-таки должен иметь правильный синтаксис. Компилятор выполнит базовую проверку синтаксиса, но затем пропустит эту часть функции на этапе конкретизации шаблона.

Именно поэтому следующий код генерирует ошибку компилятора

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

Или, может быть, вы объясните, какую цель преследует проверка правил типизации в коде, не предназначенном для исполнения? Конечно, содержательно объясните, а не в ключе "мы сделали лопату с черенком из суковатого дерева, и теперь так само естественно получается, что сучья цепляются за разные предметы".

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

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

Так почему она проверяется даже когда никогда не нужна ?

Что проверяется? Синтаксически код должен быть верным.

"не нужна в других случаях" != "никогда не нужна"

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

В раних версиях MSVC такое было, за что его все хейтили.

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

Ну, во-первых, "не компилируемый код" - немножко не верно. Не компилируются комментарии. А это - отбрасываемый на этапе компиляции код, а, значит, он должен быть синтаксически корректным.

Во-вторых, никто не отменял несовершенство компиляторов - и самого стандарта, пестрящего разными UB и UB.

И, позвольте, вы бы точно так же кричали, "Почему там выполняются базовые проверки синтаксиса?!", если бы вместо не существующих имён переменных там была бы закрывающая фигурная скобочка или объявление класса?

Директивы условной компиляции в том же самом C++ устроены именно так.

(Хотя вообще-то уже лет 50 существуют примеры, как и скобки посчитать, и макросы нормально обработать).

Да нет, у него простые подстановки, за что препроцессор в плюсах и хейтят. Заслуженно.

Но, знаете, мне не ясна ваша претензия.

Или, может быть, вы объясните, какую цель преследует проверка правил типизации в коде, не предназначенном для исполнения? Конечно, содержательно объясните, а не в ключе "мы сделали лопату с черенком из суковатого дерева, и теперь так само естественно получается, что сучья цепляются за разные предметы".

В приведённом вами участке проверки правил типизации не проводится. Там более простая проверка error: use of undeclared identifier 'aaaa'

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

Меня, например, всегда бесило подобное в интерпретируемых языках: у тебя всё работает, всё работает, всё работает, всё АААААА КАКОЕ-ТО ГОВНО В ЭТОЙ ВЕТКЕ НАПИСАНО!!! я на выход! И там действительно какая-нибудь очевидная опечатка, которую в других языках тебе бы сразу в морду лица бросили. Но тебе не бросят, пока не попадут в эту ветку. Вкуснее всего, если ошибка на финальных шагах работы какой-нить числодробилки.

Подскажите пожалуйста где можно почитать про zero_mask подробнее?

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

Да, стало лучше, чем было, но всё равно какой-то дух палеозоя витает сверху...

Уточните, что именно предлагается сделать частью языка.

Как раз вынося больше в библиотеку позволяет языку быть более простым.

Например, проверку типов аргументов. Что то наподобие

template<T = int|float|double>...

как то короче и понятнее..

Почти так и сделали (цынк):

#include <type_traits>

template<typename T>
requires (std::is_same_v<T, int> 
    || std::is_same_v<T, float>
    || std::is_same_v<T, double>)
auto my_abs(T v) {
    return v < 0 ? -v : v;
}

int main() {
    my_abs(1);
    my_abs(0.1f);
    my_abs(-0.2);
    my_abs(42ul);
}

Или принципиально важно иметь именно int|float|double вместо std::is_same_v<T,int>?

Можно даже ввести дополнительный концепт, и сделать код еще ближе к тому, что просят :)

template <typename T, typename ... Variants>
concept one_of_types = (std::is_same_v<T, Variants> || ...);

auto my_abs(one_of_types<int, float, double> auto v) {
    return v < 0 ? -v : v;
}

А вот это обязательно прописывать в коде?

template <typename T, typename ... Variants>
concept one_of_types = (std::is_same_v<T, Variants> || ...);

Компилятор не проще научить уметь в типы? Обязательно бойлерплейт тащить?

В современных реалиях - обязательно.

На месте typename могло бы быть указано значение (Non-Type Template Argument), или имя другого ограничения(концепта).
На месте concept - using, inline переменная, а то и вовсе объявление функции или типа.

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

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

using T = int;

template<T> void f();

Здесь T это не тип, а число: f<1>();

Как компилятор должен знать, что вам нужен был тип ?

ой, это, кажется, не мне, а @Racheengel ? :)

Да. Слегка промахнулся :)

Концепт будет правильней так как позволяет не вычислять типы, в случае истины, дальнейшие сравнения, т.е. работает как логическое «ИЛИ».

Либо использовать std::disjunction.

https://akrzemi1.wordpress.com/2020/03/26/requires-clause/

Надо отдать должное C++ - каждые несколько лет комитет стабильно умудряется добавить в язык новую кучу сложных абстракций которые "упрощают" код, на самом деле просто заметая детали реализации под ковёр, оставляя старые. Во времена ++11/++14 уже большинство программистов не понимало как работают шаблонные приблуды вместе с &&-фетишем. ++17/++20 добили понимание внутренностей языка и окончательно превратили его в причудливого монстра. А ещё накопилось огромное легаси, так что нужно знать всё - и как писали в 2003, и в 2011, и в 2017, и как фигачит концепты с constexpr if-ами без разбору новое поколение. Зато можно постоянно переписывать код, интересно же!
Иногда меньше - значит больше. И - нет, у Go куча своих проблем, там перегиб в другую сторону.

Вы про модули забыли :)

сылка на "Practical Modern C++ Teaser" - дохлая!!!!
Вообеще-то C++ ждут большие перемены на пути упрощения языка
Все что здесь описано делается гораздо проще с помощью Reflection
Надеюсь, что уже в следующем году мы увидим выражения "in, out, ref, inout"
https://github.com/hsutter/708/blob/main/708 talk slides.pdf

Идея сама по себе хорошая, только очень общая, как всякая абстракция. Все приведённые в презентации примеры путей подразумевают последовательное исполнение программы. Как будут маркироваться и диагностироваться переменные, используемые в нескольких процессах одновременно? И не возникнет ли проблем, если честно попытаться построить полную иерархию использования данных от прикладной программы к ядру операционной системы, где в конечном итоге зависимости придут к самопроизвольно изменяющимся аппаратным регистрам? Это всё хорошо звучит на описании класса “Лошадка”, а с таким понятием, как файл, уже могут быть проблемы.

Насколько я понимаю, минус использования if constexpr в 90% представленных здесь примеров - это то, что функции, написанные таким образом, нельзя впоследствии расширить. Тоесть, написать перегрузку для to_string для своего типа, не затрагивая изначальную функцию, больше нельзя

Всё верно. Как и многие конструкции в С++, if constexpr также не является «серебряной» пулей и не предназначена для замены механизма перегрузки функций. Следовательно её нужно использовать с головой, особенно при проектировании библиотек, которые будут использоваться сторонними программистами.
Действительно, если написать общий шаблон c диспетчеризацией, условно, что-то типа:
template <typename type_t>
type_t func(type_t arg)
{
    if constexpr (std::is_same_v<type_t, type1> {
        ...
    } else if constexpr (std::is_same_v<type_t, type2> {
        ...
    } else ...
}
такой подход, создаст массу проблем с расширением, особенно если он используется во внешнем API.
С другой стороны, для того, что бы заменить кучу бойлерплейта, построенного на частичных перегрузках классов внутри реализации, это самое то:
template<uint_t sidx, typename variant_t>
inline void print_variant(const variant_t& data, size_t idx)
{
    if constexpr (sidx < std::variant_size_v<variant_t>) {

        if (idx == 0)
            format(std::get<sidx>(data));
        else
            print_variant<sidx + 1>(data, idx - 1));
    } else
        throw v2::Exception("Variant index is out of range.");
}
— выводим значение std::variant по известному индексу в рантайме.

Мне одному кажется код из первого примера самым нормальным?

Зарегистрируйтесь на Хабре, чтобы оставить комментарий