19

The following compiles and runs (under Apple LLVM version 6.1.0 and Visual C++ 2015):

#include <functional>
#include <iostream>

struct s { int x; };

int main(int argc, char **argv)
{
    std::function<void (s &&)> f = [](const s &p) { std::cout << p.x; };
    f(s {1});
    return 0;
}

Why doesn't the assignment std::function<void (s &&)> f = [](const s &p) { std::cout << p.x; }; generate an error? A function accepting an rvalue reference should not have the same signature as a function accepting a const lvalue reference, should it? Dropping the const from the lambda's declaration does generate an error as expected.

Eagle
  • 201
  • 1
  • 4

2 Answers2

24

To expand on the existing comment and answer:

The point of std::function<R(A...)> is that it can wrap any function or functor that can be called with A... and have the result stored in an R.

So, for example,

std::function<int(int)> f = [](long l) { return l; };

is just peachy.

So what you have to ask yourself when you see something like this: if you have a lambda taking const T &, and you have an expression of type T && (or, more accurately, you have an xvalue of type T), can you use that expression to call the lambda?

Yes, you can.

And if you can, then std::function is supposed to be able to store that functor. That's pretty much the main point of std::function.

  • this also applies to class hierarchies, `std::function f = [](Base *){ };` – Ryan Haining May 14 '15 at 00:29
  • 1
    Nice answer, +1, it's the first time when I hear this. How does it work behind the scenes? Type erasure? – vsoftco May 14 '15 at 00:53
  • 1
    @vsoftco Yes, see for example [R. Martinho Fernandes's excellent answer here](http://stackoverflow.com/questions/14936539/how-stdfunction-works) for details. –  May 14 '15 at 00:57
  • @vsoft As an example, there is some object that has the described signature that std function has, and in it it calls the lambda. – Yakk - Adam Nevraumont May 14 '15 at 00:58
  • @hvd, thanks for the link, addresses exactly what I wanted to know. – vsoftco May 14 '15 at 00:59
8

Please take this with a grain of salt. This is what I understand, but I am not sure.

Consider the following output:

int main(int argc, char **argv)
{
  std::cout <<  std::is_convertible<s &&, s&>::value << std::endl;       //false                                                      
  std::cout <<  std::is_convertible<s &&, const s&>::value << std::endl; //true                                                     

  std::cout <<  std::is_convertible<const s &, s&&>::value << std::endl; //false                                                     
  return 0;
}

This shows that it is possible to convert a s && to a const s&. This is why the std::function's assignment is ok.

Dropping the const from the lambda's declaration does generate an error as expected.

Indeed this is because (as shown before), converting a s && to a s & is not possible.

In the same way, trying the opposite:

std::function<void (const s &)> f = [](s &&p) { std::cout << p.x; }; would fail because it is not possible to convert a const s& to a s &&.

Xaqq
  • 4,308
  • 2
  • 25
  • 38