Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

std::is_convertible inconsistant with std::function

Tags:

c++

std

gcc

c++11

I noticed some strange results with std::is_convertible and std::is_assignable when std::function object and std::bind are involved.

I would assume that when these functions return true, the conversion can be made. Or am I missing something?

The following code prints different results on different compilers, and I would expect it to print 0 since these types cannot be assigned.

#include <type_traits> 
#include <functional> 
#include <iostream> 

int main()
{
    std::cout << std::is_convertible<std::function<void(int)>, std::function<void()>>::value << std::endl;
}

It prints 0 on the following compilers:

  • gcc 4.8 and gcc 4.9
  • clang 3.4 (but not the one from ubuntu 12.04)

It prints 1 on the following compilers:

  • gcc 4.7
  • VC++12 (VS2013)
  • clang 3.2

Is there any correct answer? Are these bugs in compilers or am I messing with stuff that is compiler specific?

like image 245
texus Avatar asked Sep 30 '22 20:09

texus


1 Answers

In C++11, std::function's constructor taking an arbitrary functor type is specified as (quoting N3337 §20.8.11.2.1 [func.wrap.func.con]/p7):

template<class F> function(F f);
template <class F, class A> function(allocator_arg_t, const A& a, F f);

7 Requires: F shall be CopyConstructible. f shall be Callable (20.8.11.2) for argument types ArgTypes and return type R. The copy constructor and destructor of A shall not throw exceptions.

Violation of a Requires clause (passing an f not Callable for argument types ArgTypes and return type R) is undefined behavior, so the library is free to do whatever it wants in that case. The library may take the constructor out of overload resolution, but it doesn't have to, and if it doesn't, then you will have issues with overload resolution and std::is_convertible- it will report that pretty much everything under the sun is convertible to std::function (including stuff like double!).

Hence, in LWG issue 2132, the standard was modified to require implementations to remove these constructors from overload resolution (via SFINAE or a similar technique) if the functor isn't Callable for the specified argument types and return type. It now reads:

template<class F> function(F f);
template <class F, class A> function(allocator_arg_t, const A& a, F f);

7 Requires: F shall be CopyConstructible.

8 Remarks: These constructors shall not participate in overload resolution unless f is Callable (20.9.11.2) for argument types ArgTypes... and return type R.

So if your standard library implements this resolution, then std::is_convertible<std::function<void(int)>, std::function<void()>>::value is false. Otherwise, it's implementation-dependent.

I would assume that when these functions return true, the conversion can be made. Or am I missing something?

Type traits such as std::is_convertible, std::is_constructible, or std::is_assignable only considers the immediate context - i.e., whether there's a matching function signature that's accessible and not deleted. They don't check if the function's body would compile when instantiated.

like image 132
T.C. Avatar answered Oct 03 '22 00:10

T.C.