Most of the following code has been taken from an answer by Piotr Skotnicki. I was experimenting with it and discovered what I believe to be a bug in MSVC 14.0 Update 3.
Consider the following code:
#include <iostream>
template <typename T>
struct identity { using type = T; };
template <typename...>
using void_t = void;
template <typename F>
struct call_operator;
template <typename C, typename R, typename... A>
struct call_operator<R(C::*)(A...)> : identity<R(A...)> {};
template <typename C, typename R, typename... A>
struct call_operator<R(C::*)(A...) const> : identity<R(A...)> {};
template <typename F>
using call_operator_t = typename call_operator<F>::type;
template <typename, typename = void_t<>>
struct is_convertible_to_function
: std::false_type {};
template <typename L>
struct is_convertible_to_function<L, void_t<decltype(&L::operator())>>
: std::is_assignable<call_operator_t<decltype(&L::operator())>*&, L> {};
template <typename, typename = void_t<>>
struct is_callable_object
: std::false_type {};
template <typename L>
struct is_callable_object<L, void_t<decltype(&L::operator())>>
: std::true_type {};
int main()
{
auto x = []() {};
std::cout << std::boolalpha << is_callable_object<decltype(x)>::value;
std::getchar();
}
This prints true
, as expected, because the lambda object generated by the compiler does implement operator()
.
Let's now change the type parameter name in is_callable_object
from L
to T
(anything different from the type name used in is_convertible_to_function
causes this problem, from what I see).
template <typename, typename = void_t<>>
struct is_callable_object
: std::false_type {};
template <typename T>
struct is_callable_object<T, void_t<decltype(&T::operator())>>
: std::true_type {};
Suddenly, this prints false
. is_convertible_to_funtion
shouldn't matter since is_callable_object
doesn't rely on it in any way; indeed, if I remove is_convertible_to_function
, this problem disappears - I can use any type name I want.
As I said, I suspect this is a bug, so I'm asking this question to make sure that this isn't some bizarre behavior in the C++ standard; the workaround for this is quite easy.