﻿# Holy C\+\+

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


> Мы опубликовали и перевели эту статью с разрешения правообладателя\\\. Автор статьи — \[Kelbon\]\(https://habr\.com/ru/users/Kelbon/\) \\\(\[kelbonage@gmail\\\.com\]\(mailto:kelbonage@gmail\.com\)\\\)\\\. Оригинал опубликован на сайте \[Habr\]\(https://habr\.com/ru/post/672282/\)\\\.
> 
> Эта статья холиварная, но зато заставляет задуматься над разными вещами\\\. Прошу обратить внимание, что эта статья стороннего автора\\\. Мнение команды PVS\\\-Studio может отличаться от мнения автора по разным вопросам\\\.
> 
> Чтобы не пропустить интересные публикации нашей команды или гостевые посты, приглашаем \[подписаться на ежемесячную рассылку\]\(https://pvs\-studio\.ru/ru/subscribe/\)\\\.



![1010_Holy_Cpp_ru/image1.png](https://import.viva64.com/docx/blog/1010_Holy_Cpp_ru/image1.png)

## union

В первую очередь хочется убрать из языка то, что приводит к частым ошибкам и мешает развитию языка, тут идеальным кандидатом можно назвать _union_ — сумму типов из 70х\. В С идея хранения одного типа из нескольких в одном участке памяти выглядит неплохо и сейчас, ведь там все типы — это набор байт с заданным размером\.

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

```cpp
#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_\)\.

<details>
   <summary>Смотреть только если знаете С\\\+\\\+</summary>

```cpp
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();
  }
};
```


</details>


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

![1010_Holy_Cpp_ru/image2.png](https://import.viva64.com/docx/blog/1010_Holy_Cpp_ru/image2.png)

## Массивы

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

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

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

**Интересный факт**: в тексте стандарта С\+\+ есть исключение аж в цикле _for_ для сишных массивов\.\.\. Что подтверждает очевидное — массивы безумно плохо соотносятся с остальным языком\.

<details>
   <summary>Реализация массива без массива:</summary>

```cpp
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);
    }
};
```


</details>


## Тип void

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

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

## Все фундаментальные типы\.\.\.

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

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

Заменить это всё можно одним фундаментальным типом _byte_ и указателями\. Действительно: с помощью _byte_ и системы типов С\+\+ можно создавать любые типы, в том числе аналогичные _int_, _double_, _float_, _bool_ и т\.д\. из фундаментального набора\.

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

## Приведения типов из С

Это\. Просто\. Не\. Должно\. Компилироваться\. \(но оно компилируется\) [https://godbolt\.org/z/fz6eMEeqG](https://godbolt.org/z/fz6eMEeqG)

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

## Runtime variadic arguments

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

И даже несмотря на то, что так реализован знаменитый _printf\(const char\* pattern, \.\.\. \)_  \(кто не понял, многоточие — это рантайм аргументы\! Любые\!\), это выглядит самый большой костыль в истории программирования, а как этим пользоваться\.\.\. Ух\.\.\. Макросы _\_\_VA\_START\_\__,_ \_\_VA\_COPY\_\__ и громадная куча ещё всего связанного с этим будут сниться в кошмарах сишникам десятилетиями, а С\+\+ пожалуй должен просто удалить этого демона из языка и забыть \(и добавить за счёт удаления этого новые возможности пакам шаблонных аргументов\)\.

## typedef

Ну тут всё просто, в С\+\+ есть отличная замена этому слову\. Просто сравните:

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

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

## Функциональные макросы 

Это те самые макросы из С, которые принимают аргументы\. Именно их обычно называют основной причиной сложности понимания кода\. Плохого кода, ведь в современных плюсах использование подобных макросов неприемлемо\.

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

## Операторы new и delete

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

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

<details>
   <summary>СТРАШНО</summary>

![1010_Holy_Cpp_ru/image3.png](https://import.viva64.com/docx/blog/1010_Holy_Cpp_ru/image3.png)


</details>


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

## Ключевое слово class

Ну тут я просто оставлю ссылку на мою же статью про бесполезность этого ключевого слова: [https://habr\.com/ru/post/662351/](https://habr.com/ru/post/662351/)\. 

## Ключевое слово final \(запрет наследоваться от типа\)

Не имеет ни одного известного мне полезного применения, ломает обобщённый код, вердикт — удалить\.

## Виртуальные методы

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

Неэффективны, стимулируют писать архитектурно плохие решения, неэффективно использовать память, не позволяют использовать весь остальной язык, если используется ключевое слово _virtual_\. САМОЕ ГЛАВНОЕ — могут быть полностью заменены на другие языковые возможности без потери функционала и с приобретением производительности, удобства, повторяемости кода, проверок на компиляции и т\.д\.

Реализация динамического полиморфизма без виртуальных функций и их проблем: [https://github\.com/kelbon/AnyAny](https://github.com/kelbon/AnyAny)\.

## Методы \(указатель на текущий объект внутри реализации типа\)

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

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

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

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