4

please look at the following code

#include <iostream>
#include <functional>
#include <string>

int main()
{
    std::function<void(std::string&)> theFunc;
    std::string foo = "0";

    theFunc = [](std::string a) { a = "1";  };  // this compiles but has a different function signature
    theFunc(foo);
    std::cout << "foo should be 1 but is " << foo << std::endl;

    theFunc = [](auto a) { a = "2";  };         // this infers the wrong type for auto(by val not by ref), creates the wrong function signature and compiles 
    theFunc(foo);
    std::cout << "foo should be 2 but is " << foo << std::endl;

    theFunc = [](std::string& a) { a = "3";  };  // this compiles and correctly manipulates the string
    theFunc(foo);
    std::cout << "foo should be 3 and is " << foo << std::endl;

    theFunc = [](auto& a) { a = "4";  };  // this compiles and correctly manipulates the string
    theFunc(foo);
    std::cout << "foo should be 4 and is " << foo << std::endl;

    std::cin.get();
}

In the code example we have one std::function assigned different types of lambdas.

The lambda 3 i understand because the function signature matches.

But lambda 1 creates a different function signature but compiles correctly.

Lambda 2 infers the wrong auto type (by val and not by ref) and compiles correctly.

Is this a feature or a bug? What do i missunderstand regarding the function class / lambdas and the auto type inference?

UPDATE:

Thanks for the answer Handy999 but why is the following not compiling then?

    std::function<void(std::string)> theFunc2;

    theFunc2 = [](std::string& a) { a = "1";  };  // this doesn't compile and has a different function signature
    theFunc2(foo);
max66
  • 65,235
  • 10
  • 71
  • 111
Henk
  • 750
  • 10
  • 21
  • 1
    The fifth case fails because you can't call that lambda with a temporary string (i.e. `[](std::string& a) { a = "1"; }("1");` is invalid). `std::function` preserves that restriction – Caleth Feb 21 '19 at 15:45
  • Thanks Caleth, that was it. [](const std::string& a) { } compiled. – Henk Feb 21 '19 at 15:48

1 Answers1

5

Unlike function pointers, std::function takes everything which can be called as specified. If necessary, it creates a small wrapper function (in the background).

In all cases the code

void smallWrapper(std::string& s) {
    ([](std::string a) { a = "1"; })(s);
}

void smallWrapper2(std::string& s) {
    ([](auto a) { a = "2"; })(s);
}

void smallWrapper3(std::string& s) {
    ([](std::string& a) { a = "3"; })(s);
}

void smallWrapper4(std::string& s) {
    ([](auto& a) { a = "4"; })(s);
}

can be called. auto always deduces the base type, so always to std::string. Thus case 2=case 1 and case 4=case 3. This is what std::function does and what it should do.


For the 5th case, it is really as Caleth pointed out. You cannot call

([](std::string& a) { a = "5"; })("string");

since you cannot bind a reference to a temporary. (Here, the wrapper function would work. So, its not a very good model.) For const references, it works as usual:

([](const std::string& a) { a = "6"; })("string");
Handy999
  • 766
  • 4
  • 8
  • 1
    This isn't *quite* true, because the wrapper preserves the value category of the argument, which is why the updated case fails – Caleth Feb 21 '19 at 15:42
  • That's true. The value category stays. – Handy999 Feb 21 '19 at 15:54
  • 1
    I personally find this behavior of std::function extremely dangerous. With a simple typo the program manipulates a copy and not the intended object without any compile error. – Henk Feb 21 '19 at 16:24