Search code examples
c++templatestype-traits

split parameter pack in template specialization?


Is is possible to make MyTemplate<Pack1<int>, Pack2<int, double>>::value in the code works? Note that I'm not trying to write a type trait that checks if Pack2<...> starts the same as Pack1<...>. What I'm trying to do is to split the parameter of Pack2<...> into Ts1 and Ts2.

#include <iostream>

template<typename...>
class Pack1{};

template<typename...>
class Pack2 {};


template<typename...>
struct MyTemplate;

template<typename...Ts1, typename ...Ts2>
struct MyTemplate<Pack1<Ts1...>, Pack2<Ts1..., Ts2...>> {
  static constexpr bool value = true;
};


int main()
{
  // error: incomplete type is not allowed.
  std::cout << MyTemplate<Pack1<int>, Pack2<int, double>>::value; 
}

Solution

  • Your approach is flawed: you cannot deduce multiple template parameter packs from a single type, as you have attempted in Pack2<Ts1..., Ts2...>. An obvious limitation is that the compiler can't know where to end one pack and begin the other. Even if Ts1... is deduced from another template argument, that can't mismatch the deduction elsewhere.

    Possible Solution

    // Not sure why you've made two type lists; we only need one type.
    template<typename...>
    class Pack {};
    
    
    // Let's give this trait a meaningful name: IsPrefix.
    template<typename LeftPack, typename RightPack>
    struct IsPrefix
      : std::false_type {};
    
    // If the left pack is empty, it is a prefix of the right pack.
    template<typename... Ts>
    struct IsPrefix<Pack<>, Pack<Ts...>>
      : std::true_type {
        // As a side product of detecting prefixes, we can also obtain
        // a pack of all remaining types.
        using type = Pack<Ts...>;
    };
    
    // Otherwise, if two packs have a common prefix,
    // remove that prefix and examine the rest.
    template<typename Head, typename... Tail1, typename... Tail2>
    struct IsPrefix<Pack<Head, Tail1...>, Pack<Head, Tail2...>>
      : IsPrefix<Pack<Tail1...>, Pack<Tail2...>> {};
    
    // Otherwise, the base case is false.
    // See inheritance from std::false_type in the primary template.
    

    See live example at Compiler Explorer

    You can then obtain the suffix as follows:

    using L = Pack<int, double>;
    using R = Pack<int, double, float>;
    using P = IsPrefix<L, R>;
    if constexpr (P::value) {
        // P::type is Pack<float>
    }
    

    If you don't want to work with Pack in particular, you can also use template template parameters to make the IsPrefix trait work with any type list, including std::tuple and others.