Комментарии 76
А я всегда думал, что auto существует, чтобы не писать HashMapIteratorItem< HashMapIterator< HashMap<std::string, std::string> > >
Начиная с C++ 11, надо писать не enum, а enum class.
enum class byte : unsigned char ... Видимо из-за авто выведения типов сейчас и в enum указывается тип данных.
Я говорю про свои типы данных. Хотите строгой типизации - пишите enum class.
У меня к auto по большому счету одна претензия - нужно смотреть код, чтобы узнать возвращаемый результат функции или метода. И если файл для беглого просмотра открыт не в полноценной IDE с подсказками, а в простом редакторе....
Да, есть такое дело.
Зачем работать не в полноценной IDE с подсказками? Что может заставить хоть сколько-нибудь серьезный проект (состоящий не из одного файла main.cpp) разрабатывать в простом редакторе?
GitLab, например, не выводит подсказки о типе переменной во время ревью, и таких примеров много.
В целом проблема в перегруженности C++ и багажа обратной соместимости.
В том же C#, например, нет никакой проблемы с использованием var.
Что-то вы написали какие-то очень несовременные банальности, которые и без авто в языке были проблемами. Авто внёс свои, но ни одну из них вы не затронули. А вся эта игра с числами была всегда, например
float half = 1/2;
Вас же не удивляет, что здесь будет ноль?
Ну а противиться фиче через 12 лет после её внедрения, когда уже со всех сторон разобраны хорошие и плохие её применения, это как-то странно. У меня есть много кода, который без авто либо не написать вообще (сложные шаблоны), либо он будет выглядеть настолько ужасно, что читать его невозможно.
Я сам не люблю, когда авто используют слишком часто, или вообще рекоомендуют использовать везде. Слишком падает читаемость, IDE не всегда спасает. Но совсем отрицать - перебор. Моё личное требование - тип должен быть
либо написан в этой же строке кода, условно auto o = new Object();
либо быть сложным для написания, но очевидным (auto iter = vec.begin()),
либо быть очень сложным или невозможным для написания (сложные шаблонные выводы)
Имеет смысл рассматривать вопросы читаемости, не всем очевидные правила отбросывания ссылок, работу с промежуточными типами вроде vector<bool>::reference, а не вот эти наличия буковки f.
Авто внёс свои, но ни одну из них вы не затронули.
Не вопрос: затроньте и осветите, будет интересно узнать.
Просто напомню, что классический пример "сложного шаблонного вывода", где auto уместен - это произведение прямоугольных матриц произвольных размеров
Интересно, как вот такое без auto написать?
auto Sum(auto a, auto b) {
return a + b;
}
И ведь сюда не только числа можно в качестве аргументов передавать.
Когда шаблоны только появились, вот так писали, с явным указанием типа:
template <typename T> T add(T a, T b) {
return a + b;
}
int i = add<int>(2, 3);
double f = add<double>(5.0, 7.0);
И кажется, чуть позже, но еще до auto, компилятор сам научился выводить тип, так что можно было просто писать add(1, 2)
.
У меня а и b разных типов могут быть. Ну понятно, что можно через шаблоны написать. Но длиннее получится.
Так в шаблоне и несколько типов может быть - template <typename T1, typename T2>
. Ясное дело, что без auto длиннее будет, его и придумали, чтобы всё это не писать.
template <typename T1, typename T2>
decltype(std::declval<T1>() + std::declval<T2>()) add( T1 a, T2 b )
Не очень красиво, правда.
"Когда шаблоны только появились", так написать было нельзя, а когда появился std::declval
, то появился и синтаксис с trailing return type:
template <typename T, typename U>
auto add(T a, U b) -> decltype(a + b)
{
return a + b;
}
Тут, конечно, есть слово auto
, но скорее для красоты :)
Когда шаблоны только появились
Да, действительно плохо было.
Ну тогда можно было вот так поизвращаться:
template <typename T1, typename T2>
struct add_result { };
template <typename T>
struct add_result<T, T> { typedef T result_type; };
template <>
struct add_result<int, float> { typedef float result_type; };
template <typename T1, typename T2>
typename add_result<T1, T2>::result_type add( T1 a, T2 b )
{
return a + b;
}
Для какой-нибудь библиотечки, работающей с изображениями, можно быть объявить так правила работы с основными типам.
Тяжело читать фрагменты кода в статье из-за ужасного форматирования.
Нам приходится поддерживать совместимость с сильно устаревшими ОС, поэтому даже С++11 не получается использовать.
Суровый отечественный легаси.
имхо, auto не совместим с короткими названиями [x,y,z...]. Если названия несут смысл то и знать тип не обязательно.
Ну например у лямбд вообще невозможно ручками записать "нативный" тип. Да, их можно привести к std::function
, но это уже не совсем то-же самое...
зачем
писать
код
через
строчку?
Автор невероятно старательно пишет самые кривые примеры кода известные человечеству, не получает никаких проблем и потом говорит "какой ужас"
Приколов в с++ слишком много, недавно например узнал, что тренарный оператор является и rvalue и lvalue. Попробуйте туда еще auto прикрутить, может еще какие загадки появятся.
(true ? a : b) = 1; //a=1
(false ? a : b) = 1; //b=1
нет он не является и lvalue и rvalue, у вас неправильное определение rvalue как "сущность которой можно присваивать" - это неправильное определение
вместо тысячи слов можно просто скомпилировать и проверить
int a=0;
int b=0;
(true ? a : b) = 1;
std::cout << "a=" << a << "b=" << b << "\n";
(false ? a : b) = 1;
std::cout << "a=" << a << "b=" << b << "\n";
x
float f(float x) {
auto n=0.1f;
return x * n;
}
float g(float x) {
auto n=0.1;
return x * n;
}
Поразительно: если вручную расставить типы литералов, то компилятор выбирает выбранные типы.
Я вам сейчас ещё пример подкину
auto x = -1ull;
std::cout << x << std::endl;
Невероятно, но тут тоже будет не -1. Это просто разгром.
Я из другого лагеря и мне авто не хватает. Хочу авто не просто везде где сейчас можно, а вообще везде - типы параметров функций (да я знаю можно получить что хочу через лямбды, но без дедусит зис с++23 это не то, мне рекурсия часто нужна). Хочу авто в декларации мемберов класса, а тип пусть из инициализатора выводится, соглашусь с автором в одном П-последовательность с++ это нечто ни кем не виденное, статики в классах значит можно с авто, а мемберы низя, почему?
Типы они для компиляторов и они очень нужны, но людей от них надо держать подальше. Для людей есть имена/идентификаторы, вот они должны быть такими что бы вам пофигу было на тип. Конечно, всегда есть исключения вот для них и оставить явные типы.
Автор не убедил, все проблемы поднятые в статье закрываются юнит тестами, и не важно авто у вас там или явные типы. Пишу авто везде где работает уже лет 10, полет отличный.
По поводу сравнения между собой чисел с плавающей точкой - если не хотите стрелять себе в ногу, это лучше делать через приведение их к целым числам с нужной точностью, и с округлением вручную, если нужно. Например, если нужно сравнивать между собой частоты кадров, это обычно делается с точностью до одной сотой секунды (для NTSC частота кадров 29.97):
auto IsEqualFrameRate(double a, double b) {
return int(a * 100.0 + 0.5) == int (b * 100.0 + 0.5);
}
В принципе, можно "обнаглеть" и вместо double тоже написать auto. Тогда можно будет подсовывать в a и b хоть double, хоть float. Если в a и b действительно частоты кадров (которые, например, берутся из параметров видеофайлов), ни к каким проблемам это не приведет.
А использовать
std::abs(a - b) <= kEpsilon
уже не модно?
(С умножением чем-то эффективнее (нет вызова простой функции?)?)
Вариант с умножением, имхо, более универсальный - сработает на любой платформе, компиляторе (если не использовать auto, то и старом) и т.д.
Я вот смотрю описание - std::abs разве не целочисленный?
Есть способ выстрелить себе в ногу (точнее, в производительность), стараясь избежать auto. Пример отсюда:
std::map<int, int> _map;
for (const std::pair<int, int>& c : _map) {
// ...
}
В результате вместо того, чтобы пройтись по ссылкам на элементы контейнера, цикл на каждом шаге будет создавать копию элемента. А всё потому, что надо писать внутри цикла const std::pair<const int, int>&, а еще лучше const auto&
https://godbolt.org/z/7eTG5de9T
выглядит сомнительно - gcc и clang генерят одинаковый код для обоих вариантов цикла. чяднт?
long y=1;
for (auto x = 100000001.0f; x <= 100000010.0f; ++x) {
++y;
}
Что тут произойдет, знаете?
Как в бородатом анекдоте:
— Вовочка, а что можно получить, если подушку разрезать?
— Звездюлей от дедушки можно получить!
Так и тут. Итерировать дробными числами это действительно фееричный способ отстрелить себе ноги. Только какой смысл вообще рассматривать такие кейсы? Тот кто такое пишет у себя в коде либо точно знает что он делает, либо вообще не знает основ.
Если вы хаотично и бездумно смешиваете знаковые и беззнаковые, разной точности и разрядности, - то вам это auto или отказ от него - вообще не помеха для того, чтобы накосячить.
Ну вот написали auto i = arr.size()-2
, а если бы написали там size_t i = arr.size()-2
, вам что, стало бы легче?
Или если бы вы сделали проверку вида if (arr.size()-2 < 0)
вместо якобы эквивалентной if (arr.size() < 2)
?
Говнокодить - так уж говнокодить до конца.
А long long - это вообще что было? Правильный тип в данном случае - это ptrdiff_t. Который может быть, а может и не быть long long.
Опять же, если вы серьёзно следите за погрешностями в плавающей арифметике, то уж как-нибудь соблюдёте чистоту рук, и у вас в каждом конкретном месте auto будет резолвиться в тот тип, который вы ждёте и который вам очевиден. Например, вы договорились везде использовать float, чисто для детерминизма, чтобы программа грешила одним и тем же способом. (И всё равно она будет варьировать - если от оптимизации зависит порядок вычислений, а вы это пустили на самотёк).
А чтобы вы меньше лажали, форсируйте варнинги компилятора до ерроров.
Этак, наверное, можно доказать опасность вообще любого слова в языке. Если один человек сделал, то другой завсегда сломать может.
C++ со своими фичами - это когда два разных программиста могут написать код, делающий одно и то же, и при этом совершенно не понять код друг друга.
С++: стреляем по ногам по-современному