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

Но тяжелая поступь прогресса неостановима.

Прогресс принес нам современный С++ и вместе с ним новые, современные способы стрельбы себе по ногам - просто потому что стрелять по-ногам по-стариковски это фу.

В прошлый раз, как и полагается настоящему артисту, я выступил тут с выстраданным новаторским материалом, который вполне заслуженно вызвал некоторое количество эээ... скептических реакций.

По-одному из пунктов знающие люди (тм) прижали меня к стенке уничтожающим вопросом: а чего ж ты псишь на авто, мил человек?

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

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

Для начала капельку теории.

Чем использование авто отличается от прописывания типов?

Попросту говоря, если вы пишете

int х = some_expression

то результат выражения, каким бы он ни был, будет приведен к указанному вами типу, если это вообще возможно; а если вы написали

auto х = ...

то тип х будет назначен компилятором исходя из типа выражения.

Разница маленькая, но потенциально значимая.

Хватит теории, даешь практику!

Рассмотрим такой простенький код.

enum X_0{

    X0=0,X1,X2

};

enum X_1{

    X20=3,X21,X22,X23

};

  bool condition=false;

    X_0 a=X0;

    X_1 b=X20;

  Скомпилируются ли следующие выражения?

  auto c=condition?X0:X20;

    auto d=!condition?X0:X23;

    {

    auto e=!condition?X22:-120;

    auto f=condition?X0:-120;

    auto g=condition?X0:6u;

	auto h=condition?X0:0.2;

    }
Hidden text

конечно, со свистом.

Будет пара ворнингов, но в целом все пучком.

А какие у них будут типы?

Не трудитесь - у всех, кроме 2 последних, тип результата будет один и тот же, int.

То есть здесь:

auto c=condition?X0:X23;

все нормально, максимум ворнинг огребете.

Но вот такое:

X_1 d=!condition?X0:X23;

уже no pasaran; такое выражение не скомпилируется, компилятор укажет вам на ошибку. Что очень хорошо, конечно, но есть, как принято сегодня говорить, "ньюанс”…

Оказывается, выбрать из 2 совершенно чуждых друг другу типов один, затолкать его в третий, совершенно левый, похерить исходные типы и сказать, что так и должно быть - эт пожалста, вообще никаких вопросов.

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

П-последовательность.

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

Ничего хорошего.

Но это еще не все. Идем дальше.

Рассмотрим простую иерархию классов:

class A{

    virtual int f(){return 0;};

};

class B:public A{

    virtual int d(){return 0;};

};

class C:public B{

    virtual int d1(){return 0;};

};

class D:public B{

    virtual int d1(){return 0;};

};

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

{

        auto vl=condition?new B:new C; 

}

Здесь компилятор все правильно распедалит: тип результата будет конечно B*.

А тут:

{
    auto vl=condition?new D:new C; 
} 

Думаете будет B*? Нет, будет затык, компилятор сломается. 

Впрочем то же самое случится если мы запишем

{

    B* vl=condition?new D:new C; 

} 

Хотя в таком виде

{

     auto v2=condition?(B*)(new D):(B*)(new C); 

     B* v2=condition?(B*)(new D):(B*)(new C);

} 

все сработает.

Что это означает?

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

Этому конечно есть умное и логичное объяснение, но все-таки ... в чем тогда выгода от авто в конкретном сценарии?

Ни в чем.

Но это еще не все, держитесь крепче.

глядите:

void rotate_one_by_one(std::array<int, 8> &arr)

{

    auto temp = arr[arr.size() - 1];

    auto i = arr.size() - 2; // Output : see the spoiler

    

    for(; i > -1; --i)

    {

        arr[i+1] = arr[i];

    }

    arr.at(i+1) = temp;

}

void cyc_rotate(std::array<int, 8> &arr)

{

    rotate_one_by_one(arr);

    std::cout<<"After cyclic rotate\n";

    for(auto n : arr)

        std::cout<<n<<" ";

}

Output :

Hidden text

1 2 3 4 5 6 7 8

Но если мы строку

auto i = arr.size() - 2;

заменим на 

long long i = arr.size() - 2

то результат будет уже другой:

Output :

Hidden text

8 1 2 3 4 5 6 7

Этому тоже есть простое и логичное объяснение, но может лучше такой ошибки не совершать?

А вот еще:

long y=1;

for (auto x = 100000001.0f; x <= 100000010.0f; ++x) {

            ++y;

        }

Что тут произойдет, знаете?

Hidden text

Цикл никогда не закончится.

А в этом случае все будет хорошо:

for (double x = 100000001.0f; x <= 100000010.0f; ++x) {

            ++y;

        }

Причины опять же очевидны и логичны, но лучше такую ошибку не совершать, да? Да.

Разумеется, в этом месте Опытный Молодой Специалист(тм) снисходительно улыбнется и скажет: дядя, ты что, совсем дурак? вот же у тебя буковка “f” в конце числа, удали ее и будет тебе счастье.

Оно-то да, но дело видите ли в том, что: авто предназначен самостоятельно выводить типы, вместо вас. Но если вы пишете "auto х=1.0f” это означает читерство - вы подсказываете авто, какой здесь должен быть тип. Парадокс? Нет. Так оно работает. Но ведь вы желаете использовать инструмент, который должен сделать за вас вашу работу и для этого вы делаете за него его работу, то есть делаете ровно то, что должны были сделать но не хотели, потому что рассчитывали на помощь инструмента!

Парадокс? Да, теперь это парадокс.

Есть и дополнительное возражение: а что если вместо этого числа поставлена символьная константа или переменная, и вы как-то не в курсе, какой у нее тип?
Куда "f" прибивать?

Ну и заезженный мной пример: Does "auto" keyword always evaluates floating point value as double?

Здесь от невнимательного использования авто происходит ошибка округления.

Так что, может на этом можно и закруглиться?

Нет, конечно, у меня еще есть сюрпризы.

Зырьте:

#include <iostream>

#include <iomanip>

float f(float x) {

      auto n=0.1f;

      return x * n; 

}

float g(float x) { 

    auto n=0.1;

    return x * n;

}

int main() {

    auto val= 99.0f; 

    auto p1 = f (val); 

    auto p2 = g (val);

    std::cout<<std::setprecision(8)<<"p1="<<p1<<","<<"p2="<<p2<<std::endl;

    return 0;

}

Результат:

Hidden text

p1=9.9000006,p2=9.8999996

Понятно, что тут произошло?

Если нет, рекомендую почитать статью, которая прольет свет на эту мистерию:

FLP07-C. Cast the return value of a function that returns a floating-point type

Можно это как-то починить? Можно:

#include <iostream>

#include <iomanip>

float f(float x) {

      auto n=0.1f;

      return x * n; 

}

float g(float x) { 

    float n=0.1;//<-- Ключ к решению

    return x * n;

}
int main() {

    auto val= 99.0f; 

    auto p1 = f (val); 

    auto p2 = g (val);

    std::cout<<std::setprecision(8)<<"p1="<<p1<<","<<"p2="<<p2<<std::endl;

    return 0;

}

Вот теперь ништяк:

Результат: p1=9.9000006,p2=9.9000006

Ну может теперь уже хватит?

Нет! У меня есть еще!

        auto data =134.29f; 

        auto v = data;

        int dpcount = 0;

        static double EPS = 1e-3;

        while(v - (int)v > EPS){ 

		v *= 10;

        	++dpcount;

        	std::cout<<"data:"<<data<<" v-(int)v "<< std::endl; 

        }

Обратите внимание на первую строку. Достаточно стереть букву “f” и цикл никогда не закончится.

И как же это подытожить?

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

Как будто в языке без этого недостаточно грабель.

Я охотно допускаю, что есть сценарии в которых без авто ну просто смерть.

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

Ладно.

И еще некоторый недостаток в том, что авто называется именно так, перекрывая ключевое слово из богомерзкого С.

Вероятно, это проделки отчаянных борцов с легаси.

В конце концов что мешало назвать его, скажем, deduced или inferred? Вопрос, на который мы наверно никогда не узнаем ответа.

Пока-пока

Комментарии (69)


  1. MountainGoat
    15.08.2023 12:04
    +28

    А я всегда думал, что auto существует, чтобы не писать HashMapIteratorItem< HashMapIterator< HashMap<std::string, std::string> > >


    1. tri_tuza_v_karmane Автор
      15.08.2023 12:04
      -4

      Как оказалось, не только.


      1. voldemar_d
        15.08.2023 12:04
        +23

        При неаккуратном использовании float и double можно и без auto выстрелить себе в ногу.


  1. voldemar_d
    15.08.2023 12:04
    +12

    Начиная с C++ 11, надо писать не enum, а enum class.


    1. vk6677
      15.08.2023 12:04

      enum class byte : unsigned char ... Видимо из-за авто выведения типов сейчас и в enum указывается тип данных.


      1. voldemar_d
        15.08.2023 12:04
        +2

        Я говорю про свои типы данных. Хотите строгой типизации - пишите enum class.


        1. voldemar_d
          15.08.2023 12:04

          Почему-то ответы не туда попадают :(


          1. perfect_genius
            15.08.2023 12:04
            +3

            Новый баг Хабра. Ответы появляются в первом уровне ветки, но стрелки указывают на предыдущий комментарий.


            1. eton65
              15.08.2023 12:04

              Грядут улучшения?


              1. perfect_genius
                15.08.2023 12:04

                Я не из администрации. Они ещё предыдущие баги не починили, поэтому не буду пока грузить их новыми :D


      1. MiraclePtr
        15.08.2023 12:04
        +1

        Указывать тип не обязательно.

        Эта возможность опциональная, для более эффективного хранения (например, когда вы хотите засунуть enum в упакованную структуру).


  1. vk6677
    15.08.2023 12:04
    +6

    У меня к auto по большому счету одна претензия - нужно смотреть код, чтобы узнать возвращаемый результат функции или метода. И если файл для беглого просмотра открыт не в полноценной IDE с подсказками, а в простом редакторе....


    1. tri_tuza_v_karmane Автор
      15.08.2023 12:04

      Да, есть такое дело.


    1. voldemar_d
      15.08.2023 12:04
      +3

      Зачем работать не в полноценной IDE с подсказками? Что может заставить хоть сколько-нибудь серьезный проект (состоящий не из одного файла main.cpp) разрабатывать в простом редакторе?


      1. vrytov
        15.08.2023 12:04
        +6

        GitLab, например, не выводит подсказки о типе переменной во время ревью, и таких примеров много.


        1. DistortNeo
          15.08.2023 12:04
          +1

          Есть куча очевидных кейсов, типа:


          var newbornKittenExecutor = executorFactory.CreateForNewbornKitten();

          Здесь замена var на имя типа только увеличит визуальный шум.


          1. vrytov
            15.08.2023 12:04
            +1

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


    1. 0xd34df00d
      15.08.2023 12:04
      +2

      Зачем вам знать тип каждой локальной переменной?


      По крайней мере, даже на код-ревью я ловлю себя на том, что мне это не нужно.


    1. DistortNeo
      15.08.2023 12:04
      +1

      В целом проблема в перегруженности C++ и багажа обратной соместимости.
      В том же C#, например, нет никакой проблемы с использованием var.


  1. bfDeveloper
    15.08.2023 12:04
    +17

    Что-то вы написали какие-то очень несовременные банальности, которые и без авто в языке были проблемами. Авто внёс свои, но ни одну из них вы не затронули. А вся эта игра с числами была всегда, например

    float half = 1/2;

    Вас же не удивляет, что здесь будет ноль?

    Ну а противиться фиче через 12 лет после её внедрения, когда уже со всех сторон разобраны хорошие и плохие её применения, это как-то странно. У меня есть много кода, который без авто либо не написать вообще (сложные шаблоны), либо он будет выглядеть настолько ужасно, что читать его невозможно.

    Я сам не люблю, когда авто используют слишком часто, или вообще рекоомендуют использовать везде. Слишком падает читаемость, IDE не всегда спасает. Но совсем отрицать - перебор. Моё личное требование - тип должен быть

    • либо написан в этой же строке кода, условно auto o = new Object();

    • либо быть сложным для написания, но очевидным (auto iter = vec.begin()),

    • либо быть очень сложным или невозможным для написания (сложные шаблонные выводы)

    Имеет смысл рассматривать вопросы читаемости, не всем очевидные правила отбросывания ссылок, работу с промежуточными типами вроде vector<bool>::reference, а не вот эти наличия буковки f.


    1. tri_tuza_v_karmane Автор
      15.08.2023 12:04
      -4

      Авто внёс свои, но ни одну из них вы не затронули.

      Не вопрос: затроньте и осветите, будет интересно узнать.


    1. vanxant
      15.08.2023 12:04
      +1

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


      1. voldemar_d
        15.08.2023 12:04

        Интересно, как вот такое без auto написать?

        auto Sum(auto a, auto b) {
          return a + b;
        }

        И ведь сюда не только числа можно в качестве аргументов передавать.


        1. Gummilion
          15.08.2023 12:04

          Когда шаблоны только появились, вот так писали, с явным указанием типа:

          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).


          1. voldemar_d
            15.08.2023 12:04

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


            1. Gummilion
              15.08.2023 12:04

              Так в шаблоне и несколько типов может быть - template <typename T1, typename T2> . Ясное дело, что без auto длиннее будет, его и придумали, чтобы всё это не писать.


              1. 0xd34df00d
                15.08.2023 12:04
                +3

                А как вы зашаблонизируете возвращаемый тип?


                1. DistortNeo
                  15.08.2023 12:04

                  template <typename T1, typename T2>
                  decltype(std::declval<T1>() + std::declval<T2>()) add( T1 a, T2 b )

                  Не очень красиво, правда.


                  1. KanuTaH
                    15.08.2023 12:04
                    +1

                    "Когда шаблоны только появились", так написать было нельзя, а когда появился std::declval, то появился и синтаксис с trailing return type:

                    template <typename T, typename U>
                    auto add(T a, U b) -> decltype(a + b)
                    {
                        return a + b;
                    }

                    Тут, конечно, есть слово auto, но скорее для красоты :)


                    1. DistortNeo
                      15.08.2023 12:04

                      Когда шаблоны только появились

                      Да, действительно плохо было.
                      Ну тогда можно было вот так поизвращаться:


                      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;
                      }

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


                      1. voldemar_d
                        15.08.2023 12:04
                        +1

                        ИМХО, писать вот так, лишь бы не использовать auto - это почти и есть "стрелять себе в ногу". Как минимум - "есть кактусы" :-)


  1. SalazarMAX
    15.08.2023 12:04
    +22

    Тяжело читать фрагменты кода в статье из-за ужасного форматирования.


    1. perfect_genius
      15.08.2023 12:04

      Будто автор бережёт ресурс своего пробела.


      1. PereslavlFoto
        15.08.2023 12:04
        -2

        У меня вот сломался пробел. Огромная проблема, на самом деле. Пришлось менять привычку и переназначать пробел на правый-альт.


  1. romancelover
    15.08.2023 12:04
    +2

    Нам приходится поддерживать совместимость с сильно устаревшими ОС, поэтому даже С++11 не получается использовать.
    Суровый отечественный легаси.


  1. viordash
    15.08.2023 12:04
    +2

    имхо, auto не совместим с короткими названиями [x,y,z...]. Если названия несут смысл то и знать тип не обязательно.


  1. Sklott
    15.08.2023 12:04
    +7

    Ну например у лямбд вообще невозможно ручками записать "нативный" тип. Да, их можно привести к std::function, но это уже не совсем то-же самое...


    1. vk6677
      15.08.2023 12:04

      А если перед телом лямбды поставить -> int например?


      1. 0xd34df00d
        15.08.2023 12:04
        +3

        Та же проблема. У каждой лямбды свой собственный тип, и у двух разных лямбд, даже совпадающих по токенам, будут разные типы:


        void test()
        {
            auto lam1 = [] {};
            auto lam2 = [] {};
            static_assert(std::is_same_v<decltype(lam1), decltype(lam2)>);
        }

        даст вам


        error: static assertion failed due to requirement 'std::is_same_v<(lambda at <source>:5:17), (lambda at <source>:6:17)>'
            static_assert(std::is_same_v<decltype(lam1), decltype(lam2)>);
            ^             ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~


  1. MiraclePtr
    15.08.2023 12:04
    +21

    Строго говоря я псил не на авто, а на использующих его без меры людей

    ...сказал автор, после чего начал псить не на "использование без меры", а на само auto :) при этом именно "используя его без меры" - там, где auto не сильно-то и нужен и которые в обычном коде встретятся редко.
    Такое ощущение, что после прошлой статьи с наездом на auto парой совсем вымученных примеров с детскими ошибками, когда в ответ в комментариях обоснованно и аргументированно напихали полную панамку и объяснили, что конкретно с этими предъявами не так, автор обиделся и долго и отчаянно вымучивал еще больше откровенно притянутых за уши примеров.

    Рассмотрим такой простенький код.

    Ага, щаз. Вы же пишете на современном C++, как вы утверждаете, да? Тогда что у вас тут делает legacy-enum? Уже десяток лет со момента выхода C++11 из каждого утюга говорят: "используйте enum class!". И - внезапно - в этом случае никакой проблемы и нет.
    Но и громкий вброс тогда не получится, да, статью будет писать уже не о чем.

    Но если мы строку ... заменим на ... то результат будет уже другой.

    Этому тоже есть простое и логичное объяснение, но может лучше такой ошибки не совершать?

    Вы не поверите! Если мы строку

    long long i = arr.size() - 2

    заменим на

    unsigned long long i = arr.size() - 2

    то результат тоже будет другой! Безо всякого auto.
    Да, этому тоже есть простое и логичное объяснение, но может лучше такой ошибки не совершать? Давайте срочно запретим unsigned типы в C++ и будем чморить всех, кто их использует, в этом конкретном случае они не дают программисту никакого толка и повышают риск возникновения ошибок.

    Цикл никогда не закончится.

    Никаких тонкостей auto здесь нет, все предельно просто и четко: какой тип справа - такой и слева. Что присвоили, то и получили. Присвоили float - получили float. И да, вы не поверите! Если мы, опять же, строку

    for (auto x = 100000001.0f; x <= 100000010.0f; ++x) {

    заменим на

    for (float x = 100000001.0f; x <= 100000010.0f; ++x) {

    то цикл снова никогда не закончится безо всякого auto. Да, и этому тоже есть простое и логичное объяснение, но может лучше такой ошибки не совершать? Давайте выкинем из языка float-тип и составим только double, да?
    Этот пример вообще смешной - в C++ по умолчанию все литералы с плавающей точкой являются именно double, но поведение по умолчанию вас почему-то не устроило, и вы явно и специально потребовали сделать его float (добавив "f") и потом искренне удивляетесь, что компилятор работает с ним как с float до тех пор пока вы опять явно не потребуете сделать из него double.

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

    Я хотел забить маленький гвоздик, и для этого взял отбойный молоток. Получилось не очень. Вывод: отбойные молотки не нужны, от них одни проблемы.

    Ну и заезженный мной пример: Does "auto" keyword always evaluates floating point value as double?

    Похожий пример от вас разбирали в комментах к предыдущей статье (вы там создавали константу 2.5f, присваивали ее переменной, умножали эту переменную на double и удивлялись, что что-то пошло не так), и оказалось, что отказ от auto и явное прописывание типов поможет конкретно в этом случае, но в других не поможет или сделает только хуже - если вместо 2.5 в константе будет какое-то большое или наоборот очень малое число (вызывающее потерю точности при касте во float), то даже если бы разработчик явно вместо auto написал double, как хотите вы, то получил бы ошибку которую отловить было бы еще сложнее, потому что при правильном типе переменной в ней все рано была бы потеря точности из-за неявного преобразования - которое, кстати, не происходит при использовании auto (и благодаря этому auto предохраняет от целого ряда других ошибок).

    Здесь от невнимательного использования авто происходит ошибка округления.

    А что до конкретно этого примера - его там уже тоже разобрали, и выяснилось, что проблема там вообще не в auto. Даже безо всякого auto с явным указанием типов все равно можно нарваться на проблему с вызовом не той функции и огрести те же проблемы с округлением.


  1. usrsse2
    15.08.2023 12:04
    +8

    
    зачем
    
    писать
    
    код
    
    через
    
    строчку?
    
    


    1. perfect_genius
      15.08.2023 12:04
      +1

      А отсутствия пробелов не смутили, значит?

      Причём в начале они были.


      1. usrsse2
        15.08.2023 12:04

        дубликат


      1. usrsse2
        15.08.2023 12:04

        В школе в Borland C++ 3.1 я и сам писал без пробелов, потому что на экране 80x25 кода влезало очень мало. А если еще и не разворачивать cmd на полный экран, то шрифт был очень широким, поэтому арифметические выражения с пробелами выглядели глупо.


  1. Kelbon
    15.08.2023 12:04
    +6

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


    1. tri_tuza_v_karmane Автор
      15.08.2023 12:04
      -2

      Судя по результатам голосования, многие читатели нашли материал небесполезным.
      Значит он был написан не зря.


  1. dim2r
    15.08.2023 12:04
    +2

    Приколов в с++ слишком много, недавно например узнал, что тренарный оператор является и rvalue и lvalue. Попробуйте туда еще auto прикрутить, может еще какие загадки появятся.

    (true ? a : b) = 1; //a=1
    (false ? a : b) = 1; //b=1
    


    1. Kelbon
      15.08.2023 12:04

      нет он не является и lvalue и rvalue, у вас неправильное определение rvalue как "сущность которой можно присваивать" - это неправильное определение


      1. dim2r
        15.08.2023 12:04
        -2

        вместо тысячи слов можно просто скомпилировать и проверить

        
            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";


        1. Kelbon
          15.08.2023 12:04
          +2

          И что же интересно я тут должен узнать? Что тетрарный оператор ВНЕЗАПНО возвращает общий тип его двух аргументов, в данном случае int&?


    1. Kelbon
      15.08.2023 12:04

      x


    1. Leetc0deMonkey
      15.08.2023 12:04

      Тогда уж не тернарный оператор, а то что он возвращает.


  1. Ritan
    15.08.2023 12:04
    +2

    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. Это просто разгром.


    1. tri_tuza_v_karmane Автор
      15.08.2023 12:04
      -4

      Похоже, вы просто не до конца поняли с чем воюете.
      Обдумайте приведенный пример и прочтите присобаченную статью.


  1. boldape
    15.08.2023 12:04
    -1

    Я из другого лагеря и мне авто не хватает. Хочу авто не просто везде где сейчас можно, а вообще везде - типы параметров функций (да я знаю можно получить что хочу через лямбды, но без дедусит зис с++23 это не то, мне рекурсия часто нужна). Хочу авто в декларации мемберов класса, а тип пусть из инициализатора выводится, соглашусь с автором в одном П-последовательность с++ это нечто ни кем не виденное, статики в классах значит можно с авто, а мемберы низя, почему?

    Типы они для компиляторов и они очень нужны, но людей от них надо держать подальше. Для людей есть имена/идентификаторы, вот они должны быть такими что бы вам пофигу было на тип. Конечно, всегда есть исключения вот для них и оставить явные типы.

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


  1. voldemar_d
    15.08.2023 12:04

    По поводу сравнения между собой чисел с плавающей точкой - если не хотите стрелять себе в ногу, это лучше делать через приведение их к целым числам с нужной точностью, и с округлением вручную, если нужно. Например, если нужно сравнивать между собой частоты кадров, это обычно делается с точностью до одной сотой секунды (для 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 действительно частоты кадров (которые, например, берутся из параметров видеофайлов), ни к каким проблемам это не приведет.


    1. ainoneko
      15.08.2023 12:04
      +2

      А использовать

      std::abs(a - b) <= kEpsilon
      

      уже не модно?
      (С умножением чем-то эффективнее (нет вызова простой функции?)?)


      1. voldemar_d
        15.08.2023 12:04

        Вариант с умножением, имхо, более универсальный - сработает на любой платформе, компиляторе (если не использовать auto, то и старом) и т.д.


        1. TheGast
          15.08.2023 12:04

          std::numeric_limits<T>::epsilon есть в C++ вроде как ещё до C++11


      1. voldemar_d
        15.08.2023 12:04

        Я вот смотрю описание - std::abs разве не целочисленный?


        1. vanxant
          15.08.2023 12:04

          fabs конечно же должно быть)


        1. ainoneko
          15.08.2023 12:04

          Там в конце написано:

          See also
          abs(float)

          , которое ведёт на fabs.
          А в fabs :

          float abs( float num );
          double abs( double num );
          long double abs( long double num );
          (until C++23)


          1. voldemar_d
            15.08.2023 12:04

            Про fabs я в курсе. Но он выдает тоже число с плавающей точкой. С которым по-прежнему надо обращаться аккуратно.


      1. MiraclePtr
        15.08.2023 12:04

        А использовать std::abs(a - b) <= kEpsilon уже не модно?

        Давно уже не модно. Вот тут описано, как нужно правильно.


  1. voldemar_d
    15.08.2023 12:04
    +1

    Есть способ выстрелить себе в ногу (точнее, в производительность), стараясь избежать auto. Пример отсюда:

    std::map<int, int> _map;
    for (const std::pair<int, int>& c : _map) {
      // ...
    }

    В результате вместо того, чтобы пройтись по ссылкам на элементы контейнера, цикл на каждом шаге будет создавать копию элемента. А всё потому, что надо писать внутри цикла const std::pair<const int, int>&, а еще лучше const auto&


  1. artemSitnikov
    15.08.2023 12:04
    +2

    long y=1;
    for (auto x = 100000001.0f; x <= 100000010.0f; ++x) {
    ++y;
    }

    Что тут произойдет, знаете?

    Как в бородатом анекдоте:
    — Вовочка, а что можно получить, если подушку разрезать?
    — Звездюлей от дедушки можно получить!

    Так и тут. Итерировать дробными числами это действительно фееричный способ отстрелить себе ноги. Только какой смысл вообще рассматривать такие кейсы? Тот кто такое пишет у себя в коде либо точно знает что он делает, либо вообще не знает основ.


  1. nickolaym
    15.08.2023 12:04
    +1

    Если вы хаотично и бездумно смешиваете знаковые и беззнаковые, разной точности и разрядности, - то вам это 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, чисто для детерминизма, чтобы программа грешила одним и тем же способом. (И всё равно она будет варьировать - если от оптимизации зависит порядок вычислений, а вы это пустили на самотёк).

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


  1. nct123
    15.08.2023 12:04
    +1

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


  1. vitiok78
    15.08.2023 12:04

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