Search code examples
c++lambdac++14generic-lambda

std::function accepts lambda functions with arguments of different passing type (by ref, by val)


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);

Solution

  • 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");