Search code examples
c++c++11type-traitslibc++

How does the impementation for std::is_function in libcxx work?


In libcxx/include/type_traits, std::is_function is implemented in such a compact way:

namespace __libcpp_is_function_imp
{
struct __dummy_type {};
template <class _Tp> char  __test(_Tp*);
template <class _Tp> char __test(__dummy_type);
template <class _Tp> __two __test(...);
template <class _Tp> _Tp&  __source(int);
template <class _Tp> __dummy_type __source(...);
}

template <class _Tp, bool = is_class<_Tp>::value ||
                            is_union<_Tp>::value ||
                            is_void<_Tp>::value  ||
                            is_reference<_Tp>::value ||
                            __is_nullptr_t<_Tp>::value >
struct __libcpp_is_function
    : public integral_constant<bool,
                               sizeof(__libcpp_is_function_imp::__test<_Tp>(
                                      __libcpp_is_function_imp::__source<_Tp>(0))) == 1>
    {};
template <class _Tp> struct __libcpp_is_function<_Tp, true> : public false_type {};

template <class _Tp> struct _LIBCPP_TEMPLATE_VIS is_function
    : public __libcpp_is_function<_Tp> {};

I got the general idea. If a type does not match any of the non-function type (class, union, void, reference, nullptr_t), it is a function type.. However, I can't find the meaning for this line:

sizeof(__libcpp_is_function_imp::__test<_Tp>(__libcpp_is_function_imp::__source<_Tp>(0))) == 1

I think, the result type for __libcpp_is_function_imp::__source<_Tp>(0) should be _Tp&. So the result type for __libcpp_is_function_imp::__test<_Tp>(_Tp&) should be _two. And sizeof(_two) should equal to 2, which is different from 1. In other words, the equation sizeof(__libcpp_is_function_imp::__test<_Tp>(__libcpp_is_function_imp::__source<_Tp>(0))) == 1 is always false.

But I must get something wrong. Could anyone point me out?


Solution

  • Every type in C++ falls into exactly one of the following categories, possibly cv-qualified:

    • void
    • decltype(nullptr) (a.k.a. std::nullptr_t)
    • Arithmetic
    • Array
    • Pointer (i.e., T* for some type T)
    • Reference (lvalue or rvalue)
    • Pointer to non-static member
    • Enumeration
    • class or struct
    • union
    • Function

    After eliminating class, union, void, reference, and std::nullptr_t we are left with the following possible types:

    • Arithmetic
    • Array
    • Pointer
    • Pointer to non-static member
    • Enumeration
    • Function

    The remaining template metaprogramming exploits two facts about the types in these remaining categories:

    • If _Tp is an abominable function type, then the attempt to create the reference type _Tp& is ill-formed. Otherwise, _Tp& is well-formed.
    • Otherwise, the type _Tp is convertible to _Tp* if and only if _Tp is a function type, via the function-to-pointer conversion.

    It's left as an exercise for the reader to determine why class, union, void, reference, and std::nullptr_t types had to be eliminated at an earlier stage before this test would work correctly.