Каков ожидаемый результат при объявлении локальной автоматической функции?

Недавно я столкнулся с кодом C++, который, по-видимому, объявлял локальную функцию auto, и не знал, как именно ее интерпретировать.

Учитывая следующий код:

int test(int input) {
    auto f(int) -> int;
    return f(input);
}

Что именно здесь f? При каких условиях этот код имеет смысл?

Это объявление функции. Но у него нет тела, и это должно вызвать ошибку компоновщика. godbolt.org/z/8TWbxWejs Дано ли f определение где-нибудь еще?

ChrisMM 26.06.2024 19:35

Это то же самое, что int f(int); Т.е. обычное объявление функции.

Some programmer dude 26.06.2024 19:35

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

Mark Ransom 26.06.2024 19:40

Сторона, не являющаяся C++, не имеет локальных (вложенных) функций. Ближе всего к этому относятся лямбда-выражения.

Pepijn Kramer 26.06.2024 19:41

@ChrisMM Да, у этой функции есть определение в другом месте. Однако я все еще немного не понимаю, зачем вам это делать внутри функции.

Chuu 26.06.2024 19:41

@Chuu Меня это тоже смущает. За более чем 30 лет существования C++ я не видел, чтобы это использовалось где-либо еще (возможно, это предотвращает необходимость в коде загружать полный файл заголовка)

Pepijn Kramer 26.06.2024 19:43

Я все еще немного озадачен, почему вы хотите это сделать. Возможно, заголовочный файл, в котором объявлен f, не существует, и программисту не хотелось создавать заголовок (возможно, быстро и грязно, или лень). ?

PaulMcKenzie 26.06.2024 19:43

Я бы это проверил ^

NathanOliver 26.06.2024 19:44

Включите^HDОбъявите, что вы используете :-)

Jarod42 26.06.2024 19:45

Может быть, в тестовой среде связаны или определены разные функции f(), и это общий вызывающий объект?

Markus 26.06.2024 20:24
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
2
11
175
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Ответ принят как подходящий

Это объявление функции f (принимающей int и возвращающей int), которая делает ее доступной для вызова внутри test.
Он использует стиль завершающего возвращаемого типа (доступен начиная с C++11).

Это похоже на более традиционную форму объявления:

int test(int input) {
    int f(int);  // <- declaration for f
    return f(input);
}

Это позволяет вызывать f изнутри test, но не из него (при условии отсутствия глобального объявления). Но я не могу припомнить ни одного случая, когда бы это было действительно полезно.

Примечания:

  1. Это всего лишь декларация. Вам нужно определение f (с телом функции) в одной из единиц перевода, чтобы избежать ошибки компоновщика.

  2. C++ не поддерживает вложенные локальные функции. Фактическая функция f должна быть реализована вне test, например:

    int f(int n) {  
    // or: auto f(int n) -> int {
        return n*2;
    }
    
    int test(int input) {
        auto f(int) -> int;
        return f(input);
    }
    

    f также можно реализовать в другой единице перевода.

То, что вы видите, — это объявление функции (которое обычно записывается в заголовочном файле) с завершающим типом возвращаемого значения, которое поддерживается начиная с C++11. Поведение следующих двух строк кода идентично.

auto f(int) -> int;
int f(int);

cppreference для объявлений функций:

noptr-declarator ( parameter-list ) cv (необязательно) ref  (необязательно) except (необязательно) attr (необязательно)

noptr-declarator ( parameter-list ) cv (необязательно) ref (необязательно) except (необязательно) attr (необязательно) -> trailing

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

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

struct Foo {
    using T = int;

    T bar();
};

// "normal" style
Foo::T Foo::bar() {
    return 10;
}

// trailing return type, doesn't need "Foo::" twice
auto Foo::bar() -> T {
    return 10;
}

// This doesn't work. What is 'T'?
T Foo::bar() {
    return 10;
}

(Even if the last implementation would be legal, this would not compile since it defines the same thing three times.)

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

Это не «автоматическая функция». Как объяснили другие, он использует возврат поезда, но между следующими двумя объявлениями нет разницы.

auto f(int) -> int;
int f(int);

Это декларации, а не определения. Для вызова функции необходимо определение.

Почему это может быть полезно? Дело в том, что test можно определить без определения f и даже без объявления f, потому что test это предоставляет.

Отсутствие декларации может иметь множество причин. Например, f можно объявить и определить после test, как в этом примере:

int test(int input) {
    auto f(int) -> int;
    return f(input);
}

int f(int x) { 
    if (x) return test(x-1);
    return 42;
}

int main() {
    return test(1);
}

Декларацию можно было также разместить за пределами test, и в этом случае ее в просторечии называют предварительной декларацией:

auto f(int) -> int;
int test(int input) {
    return f(input);
}

Другие вопросы по теме

Как суммировать std::range?
Как распечатать неструктурные результаты функций constexpr во время компиляции с помощью clang++?
Почему take(n), используемый в istream_view, приводит к пропуску следующего токена в C++20?
Почему объявление функции глобальной области со встроенным типом arg должно быть видимым перед неквалифицированным вызовом этого имени с аргументом типа шаблона?
Есть ли способ отсортировать одну переменную-член в коллекции структур с помощью стандартной библиотеки C++?
Не могу создать std::ranges::subrange с помощью моего итератора
Трехстороннее сравнение по умолчанию генерирует больше кода, чем ожидалось
C++20 и новее – как лучше всего реализовать «перечисление с большей функциональностью»?
Неожиданный результат с `std::views::transform`, вероятно, вызван "безымянным временным"
Неявные варианты выражения понятия обрабатываются неправильно?