Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does this incorrect std::function initialization compile using MSVC?

Came across an interesting issue today started by my own typo. I created a lambda that takes in a reference to a struct and incorrectly set it to a std::function that receives it's argument by value.

Here's a more concise version:

#include <functional>

struct InputStruct
{
    int i;
    InputStruct(): i(1){}
};

void function_rcv(std::function<bool(InputStruct)> & func_ref)
{
    InputStruct in;
    func_ref(in);
}


int main()
{
    std::function<bool(InputStruct)> my_func = [](InputStruct & in)->bool{return in.i==1;};
    function_rcv(my_func);
}

Checking with godbolt shows this compiles successfully with MSVC, but fails for both Clang and GCC.

Interestingly enough, using a primitive instead of a struct fails compilation on all three compilers.

Is this a bug in the MSVC compiler?

like image 494
mascoj Avatar asked Feb 20 '19 20:02

mascoj


1 Answers

In summary: it is not a compiler bug. MSVC accepts this code because of its default non-conforming behavior, but it can be made standard-conforming with a switch.

First of all, I need to clarify std::function's one aspect: it accepts a function (in general, Callable) which signature is not a perfect match, but the parameters can be converted. Consider:

using intFn = void (int);
void fn(short);

intFn *a = fn;               // doesn't compile
std::function<intFn> b = fn; // compiles!

Here, intFn a function type which has an int parameter, while the function fn has a short parameter. The simple function pointer a, cannot be set to point to fn, as the type of the parameter differ (int vs short). But, std::function allows this, so b can be set to point to fn.

In your example, std::function has an InputStruct parameter by value, while the lambda has a non-const lvalue reference InputStruct &. When std::function std::forwards its parameter, it becomes an xvalue, which cannot be bound to the lambda's lvalue reference parameter. That's why standard conforming compilers don't accept this code.

Why does MSVC accept this code? Because it has non-conforming behavior by default: it allows binding class temporaries (and xvalues) to non-const lvalue references. You can disable this behavior with /Zc:referenceBinding (or the older /Za option). If you use this switch, MSVC rejects your example.

like image 166
geza Avatar answered Oct 21 '22 21:10

geza