How can I simulate a nested function without lambda expressions in C++11?

1.2k views Asked by At

I have the following code:

int main(int argc, char **argv)
{
    App app(800, 600);
    app.add_event_scene(Scene("Event Plot", event_plot));

    Image x("sample.png");
    struct foo { static void visual_plot() { x.draw(); } }; // Error.

    app.add_visual_scene(Scene("Visual Plot", foo::visual_plot));
    app.run();
    return 0;
}

And I get the following error:

||=== Build: Debug in Joy-Plus-Plus (compiler: GNU GCC Compiler) ===|
G:\Development\Game-Development\CB\Joy-Plus-Plus\main.cpp|54|error: use of local variable with automatic storage from containing function|
G:\Development\Game-Development\CB\Joy-Plus-Plus\main.cpp|53|error:   'Image x' declared here|
||=== Build failed: 2 error(s), 0 warning(s) (0 minute(s), 0 second(s)) ===|

I'm writing a multimedia/game engine for the Allegro 5 library, and I've abstracted the drawing part of the main-loop (As well as the event parts) into "scene" objects with plots (Functions). Each procedure is passed to the App, so that it gets "run" inside the main-loop. The problem is, the "C++ approach" does not work:

Image x("sample.png");

void visual_plot()
{
    x.draw(); // Problem.
}

int main(int argc, char **argv)
{
    App app(800, 600);
    app.add_event_scene(Scene("Event Plot", event_plot));
    app.add_visual_scene(Scene("Visual Plot", visual_plot));
    app.run();
    return 0;
}

Although the code runs, this happens:

enter image description here

And if I put the x inside the visual_plot, the image is loaded normally:

enter image description here

But now I have a huge performance problem, since a new Image object is being created at each main-loop (And it's not long until the whole thing freezes).

The image is not found when I put it outside the scope of the function because it must come after the initialization of the App, but since I have a typedef function pointer in Scene that takes that function as an argument, I also must give it a void function. The problem is that I can't create local / nested functions in C++ (After the initialization of the App). So, in order to avoid the problem, I've tried the obvious (Lambda expression / closure):

int main(int argc, char **argv)
{
    App app(800, 600);
    app.add_event_scene(Scene("Event Plot", event_plot));
    Image x("sample.png");
    app.add_visual_scene(Scene("Visual Plot", [&x]()->void{x.draw();}));
    app.run();
    return 0;
}

The problem is that the second argument of the constructor of Scene takes a function pointer:

typedef void(*plot)();
typedef map<string, plot> act;

class Scene
{
private:
    string name;
    plot p;
public:
    Scene(string name, plot p);
    ~Scene() {};
    string get_name();
    plot get_plot();
    void set_name(string value);
    void set_plot(plot value);
};

And since functions cannot be passed as parameters, and get decayed to pointers, the same also applies to the lambda expression (Which is not a function), so I get the following error:

G:\Development\Game-Development\CB\Joy-Plus-Plus\main.cpp|52|error: no matching function for call to 'Scene::Scene(const char [12], main(int, char**)::__lambda0)'|

Facing such a tragedy, how can I simulate a nested function in C++11? Since simulating like this answer does not work.

OBS: I agree that it could be a design problem, but I pretty much don't see it that way. For me, C++ just don't want me to pass that bloody function as a parameter by any means (So, I ask for the help of you long C++ Wizards).

2

There are 2 answers

1
Shoe On BEST ANSWER

Simply put the image inside the visual_plot function and make it static:

void visual_plot()
{
    static Image img("sample.png");
    x.draw(); // Problem.
}

This will initialize img the first time visual_plot is called, and only then. This will solve both the performance problem and the "it must be initialized after app.run()" issue.

0
kfsone On

It is a design problem. In order to accomplish what you are trying to do you need two pieces of information: the code to execute and the data to execute it against.

A lambda isn't magic, it simply encapsulates both of these into an object, that's why it doesn't decay nicely to a single function pointer. A lambda with captures is syntactic sugar for a function object:

int x, y;
float f;

// ...

auto l = [x, &y, f] () { return static_cast<int>((x + y) * f); };
int r = l();

is saving you from writing

struct Values {
    int x;
    int& y;
    float f;

    int operator() () {
        return static_cast<int>((x + y) * f);
    }

    Capture(int x_, int& y_, float f_) : x(x_), y(y_), f(f_) {}
};

//...

Capture c(x, y, f);

int r = c();

That's a member function call at the end there, so two pointers are involved: a pointer to the member function 'operator()' and a pointer to the instance to call it on.

int r = Capture::operator=(&c); // pseudo

Using a static or global variable you could make the address of the data known at compile time and so allow yourself to only need a function pointer.

But your design is that of a strcpy that only takes one argument or a print function that takes none: how do you know what to copy or print?

Better designs would be either to let you pass a function object to the plot functions, see how STL predicates work, which would allow both function pointers and lambdas, or use virtual functions and subclassing.

struct Scene { virtual void plot(); };
struct MyScene : public Scene {
    Image x;
    MyScene() : x("image") {}
    void plot() override { x.draw(); }
};

The pitfall of this approach is "slicing", you need to pass Scene by reference rather than by value if you are allowing derived types:

void foo(Scene& s) {
    s.plot();
}

foo(MyScene(...)); // not going to go well