Search code examples
c++templatestemplate-specializationpartial-specialization

Partial template specialization over instantiated and uninstantiated template


I have the following two structs:

template<typename T>
struct one { /* ... */ };

template<template<typename...> typename T>
struct two { /* ... */ };

When I have example instantiated/uninstantiated templates like this:

template<typename T>
struct sample_templated { /* ... */ };

using instantiated = sample_templated<double>;

then I can do

one<instantiated>{};
two<sample_templated>{};

just fine. I would like to merge the definition of one and two such that they have the same name, though, as this would allow recursion.

I tried having a default definition like

template<typename...>
struct types_match_impl;

and having the two original structs be partial specializations of this, but this is incompatible with two.

What is the solution here?


Solution

  • The way you're hoping for is not possible. Here's why:

    one<instantiated>{};
    two<sample_templated>{};
    

    one "uses" more than two: It is referring to instantiated which is sample_templated instantiated with double. two on the other hand is only "using" the sample_templated.

    When you think of templates as functions on types, then this becomes even clearer: two is a function which accepts a (type-level) function to create some type. one is a function which accepts a type to create some type:

    one :: T -> one<T>
    two :: (T -> U) -> two<(T -> U)>
    

    Put differently the parameter of one has a different "kind" ("type of type") as the one of two.

    What you can do:

    • You can provide a specialization of one which accepts a template template parameter ("type level function") and the template parameter for that:

      template<template<typename...> typename TT, typename T>
      struct one<TT<T>>  { /* ... */ }; // this basically "calls" TT
      
    • You can turn two into something which can accept both, albeit with a "dummy" template template parameter:

      template<template<typename...> typename TT, typename... Ts>
      struct two { /* */ };
      
      template<typename...>
      struct Void;
      
      template<typename T>
      struct two<Void<>, T> { /* Use T like in one */ };
      // or derive from one<T>
      

    There are probably more approaches, but these will depend on your specific use case.