Страницы

Поиск по вопросам

четверг, 5 декабря 2019 г.

Непонятный синтаксис

#cpp #cpp11


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

auto foo(int arg) -> int {} 
    


Ответы

Ответ 1



Этот синтаксис появился в результате включения в стандарт C++ лямбда-выражений. Лямбда-выражение может быть записано, например, как, auto foo = [](int arg) -> int { /*...*/ }; Это лямбда-выражение может быть преобразовано в функцию, имеющую тип int( int ) Этот синтаксис переняли для объявления функций. У функции в ее начале должен присутствовать спецификатор(ы) типа возвращаемого выражения. Так как реальный тип возвращаемого значения указывается после списка параметров, то в качестве спецификатора возвращаемого значения в объявлении функции используется спецификатор auto. В приведенном вами примере большого смысла так объявлять функцию не имеется. Но иногда тип возвращаемого значения может зависеть от типа вычисления сложного выражения, определить который программисту самостоятельно бывает трудно, да и это может привести к ошибке. Поэтому этот синтаксис удобен, например, при объявлении шаблонных функций. Рассмотрите следующую демонстрационную программу. #include template auto foo(const T &x, const U &y) -> decltype( x + y ) { return x + y; } int main() { int x = 10; int y = 20; std::cout << typeid(foo(x, y)).name() << std::endl; long z = 30; std::cout << typeid(foo(x, z)).name() << std::endl; float f = 40; std::cout << typeid(foo(x, f)).name() << std::endl; } Вывод программы на консоль, например, в MS VC++ может выглядеть как int long float Заранее сказать, какой будет тип возвращаемого выражения, невозможно. Он зависит от типов параметров функции и от типа вычисляемого выражения. Использование спецификатора типа auto в данном примере облегчает объявление функции.

Ответ 2



Он нужен, в частности, для того, чтобы поместить тип возврата в тот контекст, где видны имена параметров функции, соответственно, известна их семантика. Например auto foo(short a, short b) -> decltype(a + b) { ... } В данном случае компилятор учтет тот факт, что операнды выражения a + b будут подвергнуты integral promotions и [на большинстве платформ] дадут результат типа int. В то же время на платформах, где integral promotions для short дают unsigned int тип возврата функции автоматически станет unsigned int. (Желательна такая "гибкость" или нет - зависит от контекста.) Также при определении метода класса за пределами определения класса такой тип считается находящимся в области видимости класса, что влияет на процесс поиска неквалифицированных имен typedef void *Ret; struct A { typedef int Ret; Ret foo(); Ret bar(); }; auto A::foo() -> Ret { return 0; } // OK, `Ret` обозначает именно `A::Ret` Ret A::bar() { return 0; } // Oшибка - `Ret` обозначает `::Ret` При использовании "классического" синтаксиса в определении пришлось бы указывать квалифицированное имя типа A::Ret. Особенно существенно синтаксис с -> упрощает запись при определении методов шаблонного класса за пределами класса, ибо квалифицированное имя шаблонного класса может быть очень длинным, да еще и требовать указания ключевого слова typename. При использовании этого синтаксиса вы получаете возможность пользоваться "компактной" записью составных типов (аналогично тому, как это делается в using вместо typedef) auto foo() -> int (*)[20] { ... } В "классической" записи это выглядело бы как int (*foo())[20] { ... } что многие сочтут менее удобочитаемым вариантом.

Ответ 3



Ну, в первую очередь для шаблонов. Компилятор же может не знать возвращаемый тип до того, как увидит аргумент? Например, template ??? f(const T& t) { return t*5.0; } Что тут поставить вместо ???? А вдруг T - это класс с переопределенным оператором умножения? который возвращает объект другого класса? А вот так template auto f(const T& t) -> decltype(t*5.0) { return t*5.0; } никаких проблем...

Комментариев нет:

Отправить комментарий