4

Consider the example:

#include <iostream>
#include <functional>     // std::function
#include <vector>        // std::vector
#include <algorithm>    // std::for_each

int main(){

    auto adder = [](int x) {
        return [&](int y) { 
            return x+=y; 
        }; 
    };

    std::vector < std::function<int(int)> > vec;

    vec.push_back(adder(1));
    vec.push_back(adder(10));

    std::for_each(vec.begin(), vec.end(), [](std::function<int(int)> f){std::cout << f(33) << " ";});
    std::cout << std::endl;
}

One expects the integers 34 and 43 43 and 76, but instead gcc 4.6.0 produces "internal compiler error: Segmentation fault". What is wrong with the code?

Edit: Several other examples are discussed here.

Community
  • 1
  • 1
  • 1
    If it is an "internal compiler error" there is certainly a bug in GCC regardless whether the example is the correct way or not. – kennytm Apr 04 '11 at 19:17

3 Answers3

2

(Edit: this certainly does not explain the ICE; I read the original question too hastily.)

The One problem in that code is that the lambdas returned from the adder function contain dangling references to the x variable that no longer exists. Capture by copy ([=] or [i]) instead of a reference ([&]) and everything should work.

JohannesD
  • 13,802
  • 1
  • 38
  • 30
  • Thanks for noticing that. I capture by reference and it should work too. My error is only in that the expected outcome should not be __34__ and __43__, but rather __43__ and __76__, since this is a standard closure, i.e. it memorizes, just the way you would get it in JavaScript if you replaced C++ lambdas with function{}. But this is a minor aspect, honestly. –  Apr 04 '11 at 21:21
  • Right - unfortunately in C++ it is not possible to extend the lifetime of auto variables by capturing them in a closure; in the general case it would require garbage collection at language level, as well as storing auto variables in the heap. – JohannesD Apr 04 '11 at 23:13
  • Anyway, lambdas with associated mutable state are rather icky IMHO; they are difficult to reason about and rather diametrically opposed to the functional programming paradigm. – JohannesD Apr 04 '11 at 23:21
  • I guess the conclusion is that if you need that variable whose value is mutated across the lifetime of the lambda, and you want it to live outside the scope where it was created, you need to write a full-fledged functor. – Ken Bloom Apr 05 '11 at 20:29
  • 1
    @Ken Bloom: no, if you simply declare the lambda as `mutable`, then you can mutate variables that have been captured by value (and thus lives outside the scope where it was created) For example: `return [=](int y) mutable { return x+=y; }; ` – newacct Nov 14 '11 at 11:49
0

It seems, that in your example trailing-return-type cannot be omitted. Here is excerpt from standard (5.1.2 Lambda expressions):

If a lambda-expression does not include a trailing-return-type, it is as if the trailing-return-type denotes the following type: — if the compound-statement is of the form { attribute-specifier-seq return expression ; } the type of the returned expression after lvalue-to-rvalue conversion (4.1), array-to-pointer conversion (4.2), and function-to-pointer conversion (4.3); — otherwise, void.

Returned value in your example cannot be used for conversions mentioned above. Following code with explicitely added return type compiles in VS 2010:

auto adder = [] (int x) -> std::function<int (int)> {
  return [=]( int y ) {
    return x + y;
  };
};
Konstantin Tenzin
  • 12,398
  • 3
  • 22
  • 20
  • Thanks! That certainly helps, gcc now compiles without errors, but I still get the runtime errors on the first program I posted: –  Apr 05 '11 at 15:35
  • The run-time error is terminate called after throwing an instance of 'std::bad_function_call' what(): std::exception 144453761 144453794 Aborted Incidentally, what is a good way to write a lambda: auto m=[]()->returntype{}, or return_type m=[](){} without auto at all, or return_type m=[]()-> return_type{}? I mean in the case when I know everything explicitely. But anyway many thanks, your remark sheds a new light I just do not have enough reputation (15) to upvote it. I will be working on it and post my findings in a few days if I come up with anything. –  Apr 05 '11 at 15:43
  • I do not understand: 5.1.2 : "The evaluation of a lambda-expression results in a prvalue temporary." But if you return `x+y` it is also a prvalue temporary. So the conversion is NOT required to occur. – Earth Engine Dec 14 '11 at 05:41
  • Explain: if you return a lambda-expression within a lambda-expression the result is prvalue temporary. If you return `x+y` the result is also a prvalue temporary. So there is no reason why the first is illegal and the second is legal. – Earth Engine Dec 14 '11 at 05:44
0

You're just completely missing the point. The need for std::function is very, very obvious.

  1. All lambdas have a unique type at compile-time
  2. You want the vector to hold any functional object at run-time.
  3. Therefore, some sort of type erasure is required, which is the job std::function does.

How on earth could you ever create a vector that varies at run-time a compile-time fact, like the type contained within it? That's just logically impossible- unless you use an abstraction such as std::function.

Of course, if you only ever want one lambda type within, then you don't need std::function at all. This is relatively rare though.

int main() {
    auto adder = [](int x) {
        return [=](int y) {
            return x + y;
        };
    };
    // alternatively- you MUST copy the argument as it will cease to exist
    // but once it's in the lambda, you can use "mutable" to allow you to
    // modify the copy that each lambda has.
    /*
    auto adder = [](int x) {
        return [=](int y) mutable {
            return x += y;
        };
    };
    */
    std::vector<decltype(adder(0))> adders;
    adders.emplace_back(adder(0));
    adders.emplace_back(adder(1));

    std::for_each(adders.begin(), adders.end(), [](decltype(*adders.begin())& ref) {
        std::cout << ref(33);
    });
    std::cin.get();
}

MSVC won't actually compile this little snippet, but I think that's a bug and judging by the reports of your compiler, I expect that it will compile there and indeed work correctly.

Puppy
  • 144,682
  • 38
  • 256
  • 465
  • You provide a brilliant answer of a truly magnificent scale. I now want to read more and more about C++, thank you so much for your time. We wrote the answers at basically the same time so when I found a working trick I did not know much about your input. –  Apr 05 '11 at 20:32
  • You are right, especially on the idea level, but your program does not compile on gcc 4.6.0, same thing: g++: internal compiler error: Segmentation fault (program cc1plus). But it could work with a few modifications. I love the way you explain std::function. I will post one more answer just to clarify a few things. –  Apr 05 '11 at 20:47
  • @RustyJames: I'm pretty sure that it *should* compile, as per the latest C++0x Working Draft, but of course, implementations haven't had much time to mature and we expect many bugs. – Puppy Apr 06 '11 at 09:09