В этой статье постараюсь затронуть все вещи, которые можно без зазрения совести выкинуть из С++ не потеряв ничего(кроме боли), уменьшить стандарт, нагрузку на создателей компиляторов, студентов изучающих язык и мемосоздавательный потенциал громадности С++
В первую очередь хочется убрать из языка то, что приводит к частым ошибкам и мешает развитию языка, тут идеальным кандидатом можно назвать
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
СТРАШНО
Нужно только оставить размещающую версию оператора 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