Search code examples
c++boostc++14partial-specialization

boost::hana tag_of implementation


I wonder how the when specialization work when there's no case base for the boost::hana::when<false> case.

boost::hana::tag_of implementation:

 template<bool condition>
 struct when; // forward declaration only

 template<typename T, typename = void>
 struct tag_of;

 template<typename T, typename>
 struct tag_of : tag_of<T, when<true> >
 {};

 template<typename T, bool condition>
 struct tag_of<T, when<condition> >
 {
    using type = T;
 };

And a test example:

 struct my_tag {};
 struct my_tag2 {};

 namespace boost {
    namespace hana {

       template<class T>
       struct tag_of<T, when<std::is_same<T, int>{}()> >
       {
           using type = my_tag;
       };

       template<class T>
       struct tag_of<T, when<std::is_same<T, unsigned>{}()> >
       {
          using type = my_tag2;
       };
    }
}

int main()
{
   using type = boost::hana::tag_of<int>::type;
   std::cout << std::is_same<type, my_tag>{} << std::endl;
}

and I wonder why std::is_same<T, int>{}() (or with ::value which is the same), is a more specialized partial specialization than std::is_same<T, unsigned>{}(), and why, if the condition is false for both cases, when<condition> is more specialized.

I have done a lot of metafunctions and work with specializations and parameter packs and the sort, but in this case, there's something that I don't see.

The thing is that I don't see why the true or false value of the when template can matter, if there's no default implementation for the false case.


Solution

  • and I wonder why std::is_same<T, int>{}() (or with ::value which is the same), is a more specialized partial specialization than std::is_same<T, unsigned>{}(), and why, if the condition is false for both case, when<condition> is more specialized.

    It's not more specialized. They're simply never both viable specializations. Let's walk through what happens when we try to instantiate hana::tag_of<int>.

    1. Fill out the default template arguments. In this case, the 2nd template parameter defaults to void, so we really have hana::tag_of<int, void>.
    2. Consider which partial specializations are viable. In this case, none of them are viable - our type (int) isn't a reference or cv-qualified and it isn't any kind of when<condition>. Since none of the specializations are viable, we instantiate the primary template.
    3. The primary tag_of<T, void> inherits from tag_of<T, when<true>>. So now we need to instantiate that second type, which means we redo step #2.
    4. Now, two different specializations are viable:

      // from hana's core
      template <typename T, bool condition> 
      struct tag_of<T, when<condition>>
      
      // yours
      template <typename T>
      struct tag_of<T, when<std::is_same<T, int>{}()>>
      

      Note that your other specialization is not viable - because std::is_same<T, unsigned>{} is false and we're trying to match when<true>.

    5. Between those two specializations, yours is more specialized if we go through the partial ordering rules - so it's selected and instantiated.

    Hence, hana::tag_of<int>::type is hana::tag_of<int, void>::type is hana::tag_of<int, when<true>>::type is hana::tag_of<int, when<std::is_same<T, int>{}()>>::type is my_tag.


    The thing is that I don't see why the true or false value of the when template can matter, if there's no default implementation for the false case.

    The above hopefully illustrates why it matters. We're not instantiating when<false> directly, but you could write a specialization which substitutes into when<false> (like your when<std::is_same<T, unsigned>{}()> specialization).

    and why, if the condition is false for both cases, when<condition> is more specialized.

    Since we're instantiating when<true>, those specializations whose condition is false are simply excluded from the candidate set. The list of viable specialization is just reduced to the one that hana provides directly. It's not "more specialized" - it's solely viable.


    Also worth noting that tag_of gives you multiple ways to specialization. You can either provide some boolean condition or use void_t. Or, you could just specialize top-level:

    template <>
    struct tag_of<int, void> {
        using type = my_tag;
    };
    

    That would short circuit at step #2 above, skipping all the other instantiations.