2

This code fails to build in VC2013: (EDIT: I'm not asking why it fails to build)

#include <functional> struct MyStruct { std::function<void()> m_Func; MyStruct( const std::function<void()>& func) : m_Func(func) {} }; int main() { MyStruct rc( NULL ); return 0; } 

With error:

1>c:\program files (x86)\microsoft visual studio 12.0\vc\include\xrefwrap(283): error C2064: term does not evaluate to a function taking 0 arguments 1> c:\program files (x86)\microsoft visual studio 12.0\vc\include\functional(228) : see reference to function template instantiation '_Ret std::_Callable_obj<int,false>::_ApplyX<_Rx,>(void)' being compiled 1> with 1> [ 1> _Ret=void 1> , _Rx=void 1> ] 1> c:\program files (x86)\microsoft visual studio 12.0\vc\include\functional(228) : see reference to function template instantiation '_Ret std::_Callable_obj<int,false>::_ApplyX<_Rx,>(void)' being compiled 1> with 1> [ 1> _Ret=void 1> , _Rx=void 1> ] 1> c:\program files (x86)\microsoft visual studio 12.0\vc\include\functional(226) : while compiling class template member function 'void std::_Func_impl<_MyWrapper,_Alloc,_Ret,>::_Do_call(void)' 1> with 1> [ 1> _Alloc=std::allocator<std::_Func_class<void,>> 1> , _Ret=void 1> ] 1> c:\program files (x86)\microsoft visual studio 12.0\vc\include\functional(495) : see reference to class template instantiation 'std::_Func_impl<_MyWrapper,_Alloc,_Ret,>' being compiled 1> with 1> [ 1> _Alloc=std::allocator<std::_Func_class<void,>> 1> , _Ret=void 1> ] 1> c:\program files (x86)\microsoft visual studio 12.0\vc\include\functional(396) : see reference to function template instantiation 'void std::_Func_class<_Ret,>::_Do_alloc<_Myimpl,_Ty,_Alloc>(_Fty &&,_Alloc)' being compiled 1> with 1> [ 1> _Ret=void 1> , _Ty=int 1> , _Alloc=std::allocator<std::_Func_class<void,>> 1> , _Fty=int 1> ] 1> c:\program files (x86)\microsoft visual studio 12.0\vc\include\functional(396) : see reference to function template instantiation 'void std::_Func_class<_Ret,>::_Do_alloc<_Myimpl,_Ty,_Alloc>(_Fty &&,_Alloc)' being compiled 1> with 1> [ 1> _Ret=void 1> , _Ty=int 1> , _Alloc=std::allocator<std::_Func_class<void,>> 1> , _Fty=int 1> ] 1> c:\program files (x86)\microsoft visual studio 12.0\vc\include\functional(385) : see reference to function template instantiation 'void std::_Func_class<_Ret,>::_Reset_alloc<_Ty,std::allocator<std::_Func_class<_Ret,>>>(_Fty &&,_Alloc)' being compiled 1> with 1> [ 1> _Ret=void 1> , _Ty=int 1> , _Fty=int 1> , _Alloc=std::allocator<std::_Func_class<void,>> 1> ] 1> c:\program files (x86)\microsoft visual studio 12.0\vc\include\functional(385) : see reference to function template instantiation 'void std::_Func_class<_Ret,>::_Reset_alloc<_Ty,std::allocator<std::_Func_class<_Ret,>>>(_Fty &&,_Alloc)' being compiled 1> with 1> [ 1> _Ret=void 1> , _Ty=int 1> , _Fty=int 1> , _Alloc=std::allocator<std::_Func_class<void,>> 1> ] 1> c:\program files (x86)\microsoft visual studio 12.0\vc\include\functional(671) : see reference to function template instantiation 'void std::_Func_class<_Ret,>::_Reset<_Ty>(_Fty &&)' being compiled 1> with 1> [ 1> _Ret=void 1> , _Ty=int 1> , _Fty=int 1> ] 1> c:\program files (x86)\microsoft visual studio 12.0\vc\include\functional(671) : see reference to function template instantiation 'void std::_Func_class<_Ret,>::_Reset<_Ty>(_Fty &&)' being compiled 1> with 1> [ 1> _Ret=void 1> , _Ty=int 1> , _Fty=int 1> ] 1> f:\work\teststdfunction\teststdfunction.cpp(16) : see reference to function template instantiation 'std::function<void (void)>::function<int>(_Fx &&)' being compiled 1> with 1> [ 1> _Fx=int 1> ] 1> f:\work\teststdfunction\teststdfunction.cpp(16) : see reference to function template instantiation 'std::function<void (void)>::function<int>(_Fx &&)' being compiled 1> with 1> [ 1> _Fx=int 1> ] 

(Note the '_Fx=int' at the last two reported errors).

I can live with that, as changing MyStruct rc(NULL) to MyStruct rc(nullptr) solves the error. Two things however remain a mystery:

1) Removing the const qualifier from MyStruct ctor (MyStruct( std::function<void()>& func) gives a very, very different error:

1>f:\work\main\dev\common\teststdfunction\teststdfunction\teststdfunction.cpp(16): error C2664: 'MyStruct::MyStruct(const MyStruct &)' : cannot convert argument 1 from 'int' to 'std::function &'

Which makes more sense than the original error, and now fixing NULL to nullptr doesn't solve it. Why does int (or nullptr) refuse to cast to std::function<>& but agree to cast to a const std::function<>& ?

2) The original code compiles and works as expected in VS2010. Is it an obscure VS2010 library bug?


EDIT: As far as the const/non const question goes, I now think the casting involved and potential type mismatch are probably a red herring. The argument passed - either NULL or nullptr - is a literal and thus a const. It just cannot bind to a non const reference. For example:

const int& a = 8; // Ok int& b = 9; // error C2440: 'initializing' : cannot convert from 'int' to 'int &' 

Does that sound right? Am I still missing something?

8
  • 1
    nullptr is a value of type nullptr_t which is what the constructor of std::function accepts. NULL is not necessarily defined as nullptr and may be a different value. Use nullptr not `NULL. Commented Dec 7, 2014 at 14:24
  • 1
    As noted, I did change to nullptr. I'm not asking why the original code didn't compile. Commented Dec 7, 2014 at 14:25
  • 1
    Literals are NOT CONST AT ALL. They are rvalues. This is a totally different thing. Do not propagate that incorrect myth. Commented Dec 7, 2014 at 15:06
  • Why did you revert my edit? The modified code works in VS as well and is actually standard conforming. Commented Dec 7, 2014 at 15:13
  • @pmr - are you asking me? I didn't revert any edit, but I did add my own edit that might have accidentally overrun yours (if we did it simultaneously). My apologies. Commented Dec 7, 2014 at 15:27

2 Answers 2

2

The special here has to do with constructor template constraints and other implicit conversions.

The reason why nullptr works is because std::function has a particular constructor taking it. This constructor will always be the best match for that argument because function templates are ranked as lower preference, all else being equal.

Normally, 0 will implicitly convert to nullptr and that's fine. The problem is that it can also be passed to the unconstrained function template constructor which constructs from function objects. This does not require an implicit conversion, so all is not equal, so this constructor is preferred- which results in the error that you see that int is not a valid function object.

libstdc++ and libc++ don't not exhibit this behaviour because they have implemented the C++14 fix for this problem, which constrains the constructor. C++11 did not so this behaviour is quite conforming for a C++11 implementation.

This kind of issue is why NULL is a terrible thing that you shouldn't use. In fact, the VS2010 team had to rush nullptr out the door as an additional feature in the last minute because NULL interacts so badly with every other feature in C++, ever, and especially with C++11.

For const vs non-const references, other answers have explained that issue adequately.

There are other WTFs you can find when using std::function without the constrained constructor fix shipped in C++14- this isn't the only one. The long and short is that it's a defect in the C++11 Standard, and not in VS. VS2010 compiling it is probably a compiler overload resolution bug.

Sign up to request clarification or add additional context in comments.

2 Comments

Thanks for your answer. What I still don't understand is why SFINAE doesn't kick in. Say 0 did match the unconstrained template but failed spectacularly afterwards - shouldn't the nullptr_t specialization be tried next for a match?
@Ofek: SFINAE only applies to the function signature. It does not apply to the body generally.
0

Casting a T to a U& never works, and that's intentionally. You can cast to a U const&. The reason is that changes to the temporary U object wouldn't propagate back to the T value.

VS2010 is slightly buggy in this respect, and did allow the cast (but with the proper settings, will warn about it)

5 Comments

Not sure I understand. Obviously void f(int& i) can accept an int - doesn't that qualify as casting from T to U&?
@OfekShilon: No, it can't: f(4) is rejected despite 4 being an obvious int. And that's just T to T& - you can't change 4 either.
isn't 4 a const int? AFAIK The failure to bind 4 to an int& is double: (a) it is const, (b) it is an rvalue.
4 is const int that's why it cannot be int&. As it is mentioned it is MSVC extension. blogs.msdn.microsoft.com/vcblog/2016/11/16/permissive-switch (See: Binding a non-const reference to a temporary)
@NN_: Reasonable as it may sound, no, 4 is not a const int. It's an prvalue, in Standardese. And non-class non-array prvalues cannot be cv-qualified. See en.cppreference.com/w/cpp/language/value_category

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.