3

I came across code that passes a lambda as an argument to emplace_back, which confused me. So I wrote a small test to validate the syntax like this:

struct A
{
    int a;
    A(){cout<<"constructed"<<endl;}
    A(const A& other){a = other.a; cout<<"copied"<<endl;}
    A(const A&& other){a = other.a; cout<<"moved"<<endl;}
};

int main()
{
   vector<A> vec;
   vec.emplace_back(
       []{A a;
       a.a = 1;
       return a;   
       }());

   A a2;
   a2.a = 2;
   vec.emplace_back(std::move(a2));

  return 0;
}

I have two questions: 1) Can someone clarify how a lambda can be passed as an argument to emplace_back? I had only seen constructor arguments being passed to emplace back in the past. 2) The output was:

constructed
moved
constructed
moved
copied

Where is the last copied coming from? Why aren't the two approaches equivalent.

Haytham
  • 33
  • 3
  • 4
    It's the result of the lambda that's being emplaced. Notice the extra `()` in `}());` at the end of the lambda. The lambda is evaluated immediately. – François Andrieux Apr 27 '18 at 16:57
  • True, but does that mean that I can call any function in emplace_back, as long as the return can be passed to the constructor? – Haytham Apr 27 '18 at 16:59
  • 1
    Yes. When arguments are evaluated, if the expression is a function call, that function's result will be used. This is how all functions work, not just `emplace_back`. Be sure to understand that `emplace_back` never sees the function. It only ever sees the result. – François Andrieux Apr 27 '18 at 17:00
  • Consider reading a [good c++ book](https://stackoverflow.com/questions/388242/the-definitive-c-book-guide-and-list/388282#388282) – Passer By Apr 27 '18 at 17:11

2 Answers2

3

Can someone clarify how a lambda can be passed as an argument to emplace_back?

You are not passing a lambda. Instead, you pass the result of calling the lambda.

Where is the last copied coming from?

It comes from the vector. When a2 is inserted to vec, a reallocation occurs, which reallocates memory and copy the objects in the old memory to the new memory.

If you specify the move constructor as noexcept, i.e.

A(const A&& other) noexcept {a = other.a; cout<<"moved"<<endl;}

then std::vector will move the objects during reallocation instead of copying, and you can see the last copy becomes move.

xskxzr
  • 12,442
  • 12
  • 37
  • 77
  • Thanks for the insight about the realloc. That was it. However, the noexcept did not change the behavior. I marked the copy constructor as = delete in order to get the 'copied' to become 'moved'. Why would you expect noexcept to change that? – Haytham Apr 27 '18 at 18:56
  • @Haytham See [this answer](https://stackoverflow.com/a/8864895/5376789). Do you use VS2015 or older? – xskxzr Apr 28 '18 at 03:11
  • Thanks, that's helpful! – Haytham Apr 29 '18 at 10:51
1

Notice the () after the lambda definition, which calls the lambda. This code is equivalent to the following:

int main()
{
    auto make_a = []()
    {
        A a;
        a.a = 1;
        return a;
    };

    vector<A> vec;
    vec.emplace_back(make_a());

    // ...
}

Which, since the lambda has empty capture, is equivalent to this:

A make_a()
{
    A a;
    a.a = 1;
    return a;
}

int main()
{
    vector<A> vec;
    vec.emplace_back(make_a());

    // ...
}
Paul Belanger
  • 2,354
  • 14
  • 23