tomasgudm
tomasgudm

Reputation: 97

Captured variables in lambda functions confusion

I have an application that is creating maps for civilization V. As an interesting design choice I decided to create a couple of functions that would do the looping through the map for me. This way I could pass a function pointer or a lambda function to that function that goes through the whole map doing something to each tile. Reasoning behind this was if I or someone else would change the way the map is stored (from 2D array to a 2D vector or whatever) one would only need to change one function instead of the whole codebase.

Now the problem, here is some code first.

Error code.

    case ALL_SNOW:
        m.loop_through_limit([] (Tile* t) {
            t = new Snow(t->get_x(), t->get_y()); 
            return t;
        }, x, y, width, height);
        break;
    case PTN_ONE:
        m.loop_through_limit([&] (Tile* t) {
            int cur_x = t->get_x();
            int cur_y = t->get_y();
            t = new Plains(cur_x, cur_y);
            // if (y <= height/4 || y >= (height*3)/4) {
            //     Top quarter rows and bottom quarter rows
            //     t = new Ocean(cur_x, cur_y);
            // } else if (cur_x <= width/4) {
            //     Leftmost columns
            //     t = new Ocean(cur_x, cur_y);
            // } else if (cur_x >= (width*3)/4) {
            //     Rightmost columns
            //     t = new Desert(cur_x, cur_y);
            // } 
            return t;
        }, x, y, width, height);
        break;

Definitions from header file.

void loop_through(void (*)(Tile* t));
void loop_through_limit(Tile* (*)(Tile* t), int start_x, int start_y, int width, int height);

Now the difference in each case isn't much apart from the commented out code. This works fine. When I comment out that if statement block, then this is my output.

c++ -c  -g -O3 -ffast-math -Wall -Weffc++ -std=c++0x -o tile_block.o tile_block.cpp 
tile_block.cpp: In static member function ‘static void TileBlock::write(Map&, TileBlock::Patterns, int, int, int, int)’:
tile_block.cpp:82:35: error: no matching function for call to ‘Map::loop_through_limit(TileBlock::write(Map&, TileBlock::Patterns, int, int, int, int)::<lambda(Tile*)>, int&, int&, int&, int&)’
tile_block.cpp:82:35: note: candidate is:
map.h:26:10: note: void Map::loop_through_limit(Tile* (*)(Tile*), int, int, int, int)
map.h:26:10: note:   no known conversion for argument 1 from ‘TileBlock::write(Map&, TileBlock::Patterns, int, int, int, int)::<lambda(Tile*)>’ to ‘Tile* (*)(Tile*)’

And I believe the problem comes when I start using the parameters I'm trying to capture by reference. Then it starts to turn into a "lambda" function instead of just a "function pointer", maybe I'm just not getting it.

Any suggestions?

Upvotes: 1

Views: 954

Answers (3)

Grizzly
Grizzly

Reputation: 20191

Lambdas are typically implemented as functors (objects with an overloaded operator()). For lambas without captures the standard guarantees that they are implicitely convertible to a function pointer with the same signature (safe, since the lambda functor doesn't contain data). For lambdas with capture that is not safely possible and therefore forbidden.

In order to allow for this you need to change your loop_through and loop_through_limit method to either take std::function<void(Tile*)>:

void loop_through(std::function<void(Tile*)>);
void loop_through_limit(std::function<Tile*(Tile*)> func, int start_x, int start_y, int width, int height);

or to a template function taking any type of executable function object

template<typename F> void loop_through_limit(F func);
template<typename F> void loop_through_limit(F func, int start_x, int start_y, int width, int height);

The later approach has the advantage of lower overhead (no need to construct a std::function object), while the former approach has the advantage of not making the method a template, so it can e.g. still be virtual.

Upvotes: 2

Sarfaraz Nawaz
Sarfaraz Nawaz

Reputation: 361342

C++11 lambda are not function pointers if they capture variables. What you need is called std::function, especially for the second function, because the lambda for that capture variables.

So change these:

void loop_through(void (*)(Tile* t));
void loop_through_limit(Tile* (*)(Tile* t), /*...*/);

to these:

void loop_through(std::function<void(Tile*)>  fun);
void loop_through_limit(std::function<Tile*(Tile*)> fun, /*...*/);

Now you can pass lambda to the above functions.

Upvotes: 5

yuri kilochek
yuri kilochek

Reputation: 13486

...Then it starts to turn into a "lambda" function instead of just a "function pointer"...

Thats exactly right, Standard says that lambdas that do not capture anything can be implicitly cast to function pointers with same signature.

That you can do is make loop_through and loop_through_limit templates

template <typename F>
void loop_through(F);
template <typename F>
void loop_through_limit(F, int start_x, int start_y, int width, int height);

and call f inside.

Upvotes: 1

Related Questions