Можем ли мы иметь функции внутри функций?

Я имею в виду что-то вроде:

int main() 
{
  void a() 
  {
      // code
  }
  a();

  return 0;
}

14 ответов

Решение

Нет, C++ не поддерживает это.

Изменить: этот ответ старый. Между тем, в C++11 есть лямбды, которые могут достичь аналогичного результата - см. Ответы ниже.

Тем не менее, вы можете иметь локальные классы, и они могут иметь функции (не static или же static), так что вы можете получить это в некоторой степени, хотя это немного клудж:

int main() // it's int, dammit!
{
  struct X { // struct's as good as class
    static void a()
    {
    }
  };

  X::a();

  return 0;
}

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

Для всех намерений и целей C++ поддерживает это через лямбда-выражения:1

int main() {
    auto f = []() { return 42; };
    std::cout << "f() = " << f() << std::endl;
}

Вот, f является лямбда-объектом, который действует как локальная функция в main, Захваты могут быть определены, чтобы позволить функции получить доступ к локальным объектам.

За кулисами, f является функциональным объектом (т.е. объектом типа, который обеспечивает operator()). Тип объекта функции создается компилятором на основе лямбды.


1 с C++11

Локальные классы уже упоминались, но вот способ, позволяющий им появляться в большей степени как локальными функциями, используя перегрузку operator() и анонимный класс:

int main() {
    struct {
        unsigned int operator() (unsigned int val) const {
            return val<=1 ? 1 : val*(*this)(val-1);
        }
    } fac;

    std::cout << fac(5) << '\n';
}

Я не советую использовать это, это просто забавный трюк (может, но imho не должен).


Обновление 2014 года:

С появлением C++11 некоторое время назад вы можете иметь локальные функции, синтаксис которых немного напоминает JavaScript:

auto fac = [] (unsigned int val) {
    return val*42;
};

Вы не можете иметь локальные функции в C++. Однако в C++11 есть лямбды. Лямбды в основном переменные, которые работают как функции.

Лямбда имеет тип std::function (на самом деле это не совсем так, но в большинстве случаев можно предположить, что это так). Чтобы использовать этот тип, вам нужно #include <functional>, std::function шаблон, принимающий в качестве аргумента шаблона тип возвращаемого значения и типы аргумента с синтаксисом std::function<ReturnType(ArgumentTypes), Например, std::function<int(std::string, float)> лямбда возвращает int и принимая два аргумента, один std::string и один float, Наиболее распространенным является std::function<void()>, который ничего не возвращает и не принимает аргументов.

Как только лямбда объявлена, она вызывается так же, как обычная функция, используя синтаксис lambda(arguments),

Чтобы определить лямбду, используйте синтаксис [captures](arguments){code} (есть другие способы сделать это, но я не буду упоминать их здесь). arguments есть какие аргументы лямбда принимает, и code это код, который должен быть запущен при вызове лямбды. Обычно вы кладете [=] или же [&] как захватывает. [=] означает, что вы перехватываете все переменные в области, в которой значение определяется значением, что означает, что они сохранят значение, которое они имели при объявлении лямбды. [&] означает, что вы перехватываете все переменные в области видимости по ссылке, что означает, что они всегда будут иметь свое текущее значение, но если они будут удалены из памяти, программа потерпит крах. Вот некоторые примеры:

#include <functional>
#include <iostream>

int main(){
    int x = 1;

    std::function<void()> lambda1 = [=](){
        std::cout << x << std::endl;
    };
    std::function<void()> lambda2 = [&](){
        std::cout << x << std::endl;
    };

    x = 2;
    lambda1();    //Prints 1 since that was the value of x when it was captured and x was captured by value with [=]
    lambda2();    //Prints 2 since that's the current value of x and x was captured by value with [&]

    std::function<void()> lambda3 = [](){}, lambda4 = [](){};    //I prefer to initialize these since calling an uninitialized lambda is undefined behavior.
                                                                 //[](){} is the empty lambda.

    {
        int y = 3;    //y will be deleted from the memory at the end of this scope
        lambda3 = [=](){
            std::cout << y << endl;
        };
        lambda4 = [&](){
            std::cout << y << endl;
        };
    }

    lambda3();    //Prints 3, since that's the value y had when it was captured

    lambda4();    //Causes the program to crash, since y was captured by reference and y doesn't exist anymore.
                  //This is a bit like if you had a pointer to y which now points nowhere because y has been deleted from the memory.
                  //This is why you should be careful when capturing by reference.

    return 0;
}

Вы также можете захватить определенные переменные, указав их имена. Просто указав их имя будет захватывать их по значению, указав их имя с & прежде чем захватить их по ссылке. Например, [=, &foo] будет захватывать все переменные по значению, кроме foo который будет захвачен ссылкой, и [&, foo] захватит все переменные по ссылке, кроме foo который будет захвачен по значению. Вы также можете захватить только определенные переменные, например, [&foo] будет захватывать foo по ссылке и не будет захватывать никаких других переменных. Вы также можете захватить без переменных вообще с помощью [], Если вы попытаетесь использовать переменную в лямбде, которую вы не захватили, она не скомпилируется. Вот пример:

#include <functional>

int main(){
    int x = 4, y = 5;

    std::function<void(int)> myLambda = [y](int z){
        int xSquare = x * x;    //Compiler error because x wasn't captured
        int ySquare = y * y;    //OK because y was captured
        int zSquare = z * z;    //OK because z is an argument of the lambda
    };

    return 0;
}

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

#include <functional>

int main(){
    int x = 3, y = 5;
    std::function<void()> myLambda = [x, &y](){
        x = 2;    //Compiler error because x is captured by value and so it's of type const int inside the lambda
        y = 2;    //OK because y is captured by reference
    };
    x = 2;    //This is of course OK because we're not inside the lambda
    return 0;
}

Кроме того, вызов неинициализированных лямбда-выражений является неопределенным поведением и обычно вызывает сбой программы. Например, никогда не делайте так:

std::function<void()> lambda;
lambda();    //Undefined behavior because lambda is uninitialized

Примеры

Вот код для того, что вы хотели сделать в своем вопросе, используя лямбды:

#include <functional>    //Don't forget this, otherwise you won't be able to use the std::function type

int main(){
    std::function<void()> a = [](){
        // code
    }
    a();
    return 0;
}

Вот более продвинутый пример лямбды:

#include <functional>    //For std::function
#include <iostream>      //For std::cout

int main(){
    int x = 4;
    std::function<float(int)> divideByX = [x](int y){
        return (float)y / (float)x;    //x is a captured variable, y is an argument
    }
    std::cout << divideByX(3) << std::endl;    //Prints 0.75
    return 0;
}

Нет.

Что ты пытаешься сделать?

обходной путь:

int main(void)
{
  struct foo
  {
    void operator()() { int a = 1; }
  };

  foo b;
  b(); // call the operator()

}

Старый ответ: Вы можете, вроде как, но вы должны обмануть и использовать фиктивный класс:

void moo()
{
    class dummy
    {
    public:
         static void a() { printf("I'm in a!\n"); }
    };

    dummy::a();
    dummy::a();
}

Более новый ответ: более новые версии C++ также поддерживают лямбды, чтобы сделать это лучше / правильно. Смотрите ответы выше на странице.

Нет, это не разрешено Ни C, ни C++ не поддерживают эту функцию по умолчанию, однако TonyK указывает (в комментариях), что существуют расширения для компилятора GNU C, которые включают это поведение в C.

Вы не можете определить свободную функцию внутри другой в C++.

Как уже упоминали другие, вы можете использовать вложенные функции, используя расширения языка gnu в gcc. Если вы (или ваш проект) придерживаетесь цепочки инструментов gcc, ваш код будет в основном переносимым между различными архитектурами, предназначенными для компилятора gcc.

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


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

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

Печально, что C/C++ не поддерживает такие функции, как стандарт. Большинство Паскаля и Ада делают (почти все языки на основе Алгола). То же самое с JavaScript. То же самое с современными языками, такими как Scala. То же самое с почтенными языками, такими как Erlang, Lisp или Python.

И, как и в случае с C/C++, к сожалению, Java (на которой я зарабатываю большую часть своей жизни) - нет.

Я упоминаю здесь Java, потому что вижу несколько плакатов, предлагающих использование классов и методов классов в качестве альтернативы вложенным функциям. И это также типичный обходной путь в Java.

Краткий ответ: Нет.

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

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

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

Все эти приемы выглядят (более или менее) как локальные функции, но они не работают так. В локальной функции вы можете использовать локальные переменные ее суперфункций. Это своего рода полуглобальные. Ни один из этих трюков не может этого сделать. Наиболее близким является лямбда-трюк из C++0x, но его закрытие ограничено временем определения, а не временем использования.

Позвольте мне опубликовать здесь решение для C++03, которое я считаю наиболее чистым из возможных. *

#define DECLARE_LAMBDA(NAME, RETURN_TYPE, FUNCTION) \
    struct { RETURN_TYPE operator () FUNCTION } NAME;

...

int main(){
  DECLARE_LAMBDA(demoLambda, void, (){ cout<<"I'm a lambda!"<<endl; });
  demoLambda();

  DECLARE_LAMBDA(plus, int, (int i, int j){
    return i+j;
  });
  cout << "plus(1,2)=" << plus(1,2) << endl;
  return 0;
}

(*) в мире C++ использование макросов никогда не считается чистым.

Но мы можем объявить функцию внутри main():

int main()
{
    void a();
}

Хотя синтаксис правильный, иногда это может привести к "Самому неприятному анализу":

#include <iostream>


struct U
{
    U() : val(0) {}
    U(int val) : val(val) {}

    int val;
};

struct V
{
    V(U a, U b)
    {
        std::cout << "V(" << a.val << ", " << b.val << ");\n";
    }
    ~V()
    {
        std::cout << "~V();\n";
    }
};

int main()
{
    int five = 5;
    V v(U(five), U());
}

=> нет вывода программы.

(Только Clang предупреждение после компиляции).

C++ самый неприятный анализ снова

Да, и вы можете делать с ними то, что не поддерживают даже C++20 Lambdas. А именно, чисто рекурсивные вызовы самих себя и связанных функций.

Например, гипотеза Коллатца состоит в том, что некая простая рекурсивная функция в конечном итоге выдаст «1» для ЛЮБОГО положительного целого числа N. Используя явную локальную структуру и функции, я могу написать одну автономную функцию для запуска теста для любого «N ".

      constexpr std::optional<int> testCollatzConjecture(int N) { 
    struct CollatzCallbacks {
        constexpr static int onEven(int n) {
            return recurse(n >> 1); // AKA "n/2"
        }
        constexpr static int onOdd(int n) {
            if(n==1) return 1;     // Break recursion. n==1 is only possible when n is odd.
            return recurse(3 * n + 1);
        }
        constexpr static int recurse(int n) {
            return (n%2) ? onOdd(n) : onEven(n); // (n%2) == 1 when n is odd                
        }
    };

    // Error check
    if(N < 0) return {};

    // Recursive call.
    return CollatzCallbacks::recurse(N);
}

Обратите внимание на некоторые вещи, которые здесь не могут сделать даже лямбда-выражения С++20:

  1. Мне не нужны std::function<> клеи ИЛИ лямбда-захваты ("[&]") только для того, чтобы мои локальные рекурсивные функции вызывали себя или друг друга. Мне нужно было 3 обычные функции с именами, и это все, что мне нужно было написать.
  2. Мой код более читабелен и (из-за (1)) также будет работать намного быстрее.
  3. Я четко отделяю рекурсивную логику в «CollatzCallbacks» от остальной части «testCollatzConjecture». Все это работает в изолированной песочнице.
  4. Я смог сделать все «constexpr» и без состояния, поэтому все это может запускаться во время компиляции для любого постоянного значения. Насколько мне известно, мне понадобится С++23 только для выполнения рекурсивной части с лямбда-выражениями без состояния.

Помните: лямбда-функции на самом деле представляют собой просто локальные структуры, сгенерированные компилятором, такие как «CollatzCallbacks», только они безымянные и имеют только одну функцию-член «operator()». Вы всегда можете написать более сложные локальные структуры и функции напрямую, особенно в таких случаях, когда они вам действительно нужны.

Когда вы пытаетесь реализовать функцию в другом теле функции, вы должны получить это error как незаконное определение:

error C2601: 'a' : local function definitions are illegal
IntelliSense: expected a ';'

Так что не пытайтесь снова.

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