Holy C++

в 17:10, , рубрики: c++, C++20, ненормальное программирование, Совершенный код

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

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

1 - union - сумм тип из 70х, в С идея хранения одного типа из нескольких в одном участке памяти выглядит неплохо и сейчас, ведь там все типы это набор байт с заданным размером.

В С++ же использование union это автоматическое undefined behavior, например:

#include <string>
union A { int x; float y;};
union B {
  B() {} // требуется написать какой то конструктор и деструктор
  // но обратите внимание, что написать деструктор правильно невозможно
  // (попробуйте, если не верите)
  ~B() {}
  std::string s;
  int x;
};
int main() {
  A value;
  value.x = 5;
  value.y; // undefined behavior, обращение к неактивному члену union
  B value2;
  value2.s = "hello world";
  // undefined behavior, поле s неактивно и используется 
  // (в операторе= для std::string)
}

Как вы видите использовать union без ошибок просто невозможно, при этом вам постоянно придётся вручную вызывать правильный деструктор для объекта и вместо приравнивания делать placement new в нужное поле. Так зачем же так мучаться, если можно сделать нормальный тип с хорошим интерфейсом БЕЗ какого либо оверхеда относительно юниона?

Следующий код полностью заменяет юнион, не имеет никакого оверхеда относительно него и имеет более понятный пользователю интерфейс (emplace / destroy)

Смотреть только если знаете С++
template<typename ... Ts>
struct union_t {
  alignas(std::ranges::max({alignof(Ts)...})
  std::byte data[std::ranges::max({sizeof(Ts)...});
  
  template<one_of<Ts...> U>
  constexpr U& emplace(auto&&... args) {
    return std::launder(new(data) U{std::forward<decltype(args)>(args)....});
  }
  template<one_of<Ts...> U>
  constexpr void destroy_as() const {
    reintepret_cast<const U*>(reinterpret_cast<void*>(data))->~U();
  }
};

При этом в стандарте просто колоссальное количество исключений для union, бесполезных правил и ограничений. А можно просто взять и забыть про этот отголосок С, в 2022-то году...

2 - массивы

Это может звучать странно, но мы правда можем убрать из С++ массивы не потеряв ничего (убрав этот чудовищный синтаксис char(&&...arr)[N] (угадайте в комментариях что это значит) )

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

Как же их заменить? Рекусивным(или через множественное наследование) туплом с элементами одного типа(да, это было очевидно)))

Интересный факт:

в тексте стандарта С++ есть исключение аж в цикле for для сишных массивов... Что подтверждает очевидное - массивы безумно плохо соотносятся с остальным языком

Реализация массива без массива
template<typename T, size_t I>
struct array_value { T value; };
template<typename, typename>
struct array_impl;
template<typename T, size_t... Is>
struct array_impl<T, std::index_sequence<Is...>> : array_value<T, Is>...{};

template<typename T, size_t N>
struct array_ : array_impl<T, std::make_index_sequence<N>> {
  // тут какой-то интерфейс массива по вашему желанию
    T& operator[](size_t n) {
        // такая реализация для краткости
        return *(reinterpret_cast<T*>(reinterpret_cast<void*>(this)) + n);
    }
};

3 - тип void

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

struct [[maybe_unused]] nulltype {};
 // Вот и всё... Да и аттрибут [[maybe_unused]] тут разве что для красоты

4 - все фундаментальные типы...
Кажется мы идём по нарастающей, на что же автор статьи тут замахнулся? На int?!

Да, не удались в С фундаментальные типы, а С++ их унаследовал. Кто в здравом уме будет использовать int, который может занимать 8 байт, но гарантирует свои значения только до 2 ^ 16??? Это буквально создатель ошибок(особенно у новичков)

Заменить это всё можно одним фундаментальным типом byte и указателями, действительно: с помощью byte и системы типов С++ можно создавать любые типы, в том числе аналогичные int, double, float, bool и т.д. из фундаментального набора
Тут мы убиваем сразу несколько зайцев - нет больше исключений для фундаментальных типов в разрешении перегрузки, нет исключений в шаблонном коде для наследования( от фундаментальных типов нельзя наследоваться) ну и другие более мелкие исключения для подобных типов уходят в прошлое

4.5 - приведения типов из С - это.просто.не.должно.компилироваться. (но оно компилируется) https://godbolt.org/z/fz6eMEeqG

int main() {
    (void)(5), (void)5, void(5);
}

5 - runtime variadic arguments - человек, который придумал эту вещь в С должно быть сейчас раскаивается за этот грех, но нам приходится его тянуть.

И даже не смотря на то, что так реализован знаменитый printf(const char* pattern, ... ) <- кто не понял, многоточие это рантайм аргументы! Любые! Это выглядит самый большой костыль в истории программирования, а как этим пользоваться... Ух... макросы __VA_START__ __VA_COPY__ и громадная куча ещё всего связанного с этим будут сниться в кошмарах сишникам десятилетиями, а С++ пожалуй должен просто удалить этого демона из языка и забыть(и добавить за счёт удаления этого новые возможности пакам шаблонных аргументов)

6 - typedef - ну тут всё просто, в С++ есть отличная замена этому слову, просто сравните:

typedef void(*foo)(int); // foo теперь алиас на void(*)(int) (указатель на функцию)
// то же самое, но на С++
using foo = void(*)(int);

Смысла оставлять typedef в языке нет...))

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

Вот, кажется на этом этапе мы вычистили почти весь С из С++ и почти получили чистые ++(плюсы). Пора рассмотреть что стоит удалить тут!

8 - операторы new и delete

Действительно, зачем нужны в языке эти операторы, если всё давно перенесено на уровень абстракций аллокаторов, а память на низком уровне можно продолжать выделять через malloc?! Как вообще можно было догадаться внести систему(системный аллокатор памяти) на уровень языка?

Вы только посмотрите на даже не правила, а просто список перегрузок одного только new

СТРАШНО
Holy C++ - 1

Нужно только оставить размещающую версию оператора new для вызова конструктора по нужному адресу, всё остальное, особенно перегрузки new / delete использовать в современном С++ просто запрещено, если вы не хотите чтобы вас засмеяли

9 - ключевое слово class - ну тут я просто оставлю ссылку на мою же статью про бесполезность этого ключевого слова https://habr.com/ru/post/662351/

10 - ключевое слово final (запрет наследоваться от типа) - не имеет ни одного известного мне полезного применения, ломает обобщённый код, вердикт - удалить

11 - виртуальные методы :

Вызывают громадную кучу ошибок

Неэффективны, стимулируют писать архитектурно плохие решения, неэффективно использовать память, не позволяют использовать весь остальной язык, если используется ключевое слово virtual, и САМОЕ ГЛАВНОЕ - могут быть полностью заменены на другие языковые возможности без потери функционала(и с приобретением производительности, удобства, повторяемости кода, проверок на компиляции и т.д....)
Реализация динамического полиморфизма без виртуальных функций и их проблем: https://github.com/kelbon/AnyAny

12 - методы ( указатель на текущий объект внутри реализации типа )

В С++23 появляется(наконец) deducing this, благодаря которому можно будет явно декларировать передачу this в методы типа, при этом такой "метод" будет фактически функцией(с точки зрения языка), а значит в последующем(вместе с удалением виртуальных методов) можно будет избавиться от самого понятия МЕТОД в языке С++(и указателя на эту вещь) (не дай боже вам перед сном увидеть декларацию указателя на метод)

struct A {
void foo(this A& self);
};

При этом возможно, что постепенно и ключевое слово this потеряет прежнее значение и останется только такое - декларация явной передачи ссылки/значения типа в функцию

Ну вот и всё, помечтали о великолепном hole C++, можете теперь пойти и опять продолжить писать хрень с виртуальным наследованием, забытым виртуальным деструктором на полиморфном типе и сишными кастами, удачи...

Автор:
Kelbon

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js