Search code examples
c++template-meta-programmingsfinae

How does C++ template specialization work with default boolean value?


I am looking through libc++'s code and I have noticed this snippet:

// __pointer
_LIBCPP_ALLOCATOR_TRAITS_HAS_XXX(__has_pointer, pointer);
template <class _Tp, class _Alloc,
          class _RawAlloc = __libcpp_remove_reference_t<_Alloc>,
          bool = __has_pointer<_RawAlloc>::value>
struct __pointer {
    using type _LIBCPP_NODEBUG = typename _RawAlloc::pointer;
};
template <class _Tp, class _Alloc, class _RawAlloc>
struct __pointer<_Tp, _Alloc, _RawAlloc, false> {
    using type _LIBCPP_NODEBUG = _Tp*;
};

What puzzled me is this line:

bool = __has_pointer<_RawAlloc>::value

The semantics of this code are pretty clear: here we call a metafunction __has_pointer and if it is true, we go with the first implementation of __pointer and if it's not we go with the second (the one with an explicit false in its template parameter). I am confused because I don't understand how this works, shouldn't the first instance have an explicit true in its template params and then template specialization kicks in? If that's the case then we need to call the metafunction when we initiate this template, so maybe bool = __has_pointer<_RawAlloc>::value> is a shorthand for that? I am wondering what kind of mechanism is used here so that this is allowed. What are the rules that are used to make this work?

Full implementation of _LIBCPP_ALLOCATOR_TRAITS_HAS_XXX can be found here:

#define _LIBCPP_ALLOCATOR_TRAITS_HAS_XXX(NAME, PROPERTY)            \
template <class _Tp, class = void> struct NAME : false_type { };    \
template <class _Tp>               struct NAME<_Tp, __void_t<typename _Tp:: PROPERTY > > : true_type { }

Solution

  • Shouldn't the first instance has an explicit true in its template params and then template specialization kicks in?

    First, you define a template. This "base" template is what you get if you attempt to instantiate a template.

    Then you get to define a specialization. If the template parameters match the specialization's then you get the specialized template. Surprise!

    template <class _Tp, class _Alloc,
              class _RawAlloc = __libcpp_remove_reference_t<_Alloc>,
              bool = __has_pointer<_RawAlloc>::value>
    struct __pointer
    

    Let's ignore the specialization. Without the specialization this is the template you'll get when you instantiate this template. This is what you get when the last template parameter turns out to be either true, or false. It doesn't matter. This is your template. Enjoy it. Bon appetit.

    But wait, you forgot: you also have a specialization. Hold your horses: if the last template parameter turns out to be false, your meal will be the specialization.

    However, if the last template parameter turns out to be true nothing changes, you still get the original template. Nothing needs to be set to an "explicit true" elsewhere.

    Having said that, yes, you can actually make things look this way, by having two specializations, and not even defining the base template:

    template <class _Tp, class _Alloc,
              class _RawAlloc = __libcpp_remove_reference_t<_Alloc>,
              bool = __has_pointer<_RawAlloc>::value>
    struct __pointer;
    
    template <class _Tp, class _Alloc, class _RawAlloc>
    struct __pointer<_Tp, _Alloc, _RawAlloc, true> {
        using type _LIBCPP_NODEBUG = typename _RawAlloc::pointer;
    };
    
    template <class _Tp, class _Alloc, class _RawAlloc>
    struct __pointer<_Tp, _Alloc, _RawAlloc, false> {
        using type _LIBCPP_NODEBUG = _Tp*;
    };
    

    This is logically equivalent. Both alternatives are logically equivalent to each other.