Search code examples
c++c++11templatesvariadic-templatestemplate-templates

Deduce/erase type of template template argument


When using template template arguments how can I have the template type of the template template deduced or erased?

Consider the following SSCCE:

#include <cstdint>
#include <cstddef>
#include <iostream>
using namespace std;

template<int i>
struct Value { };

template<int i>
struct BadValue { };

template<typename... G>
struct Print;

template<template<int> class ValueType, int... Is>
struct Print< ValueType<Is>... > {
    static void print() {
        const int is[] = { Is... };
        for (int i: is)
            cout << i;
        cout << endl;
    }

};

using V1 = Value<1>;
using V2 = Value<2>;
using V3 = Value<3>;
using BV = BadValue<1>;

int main() {
    Print<V2, V1, V2, V3>::print(); // <-- fine
    Print<V2, V1, V2, BV>::print(); // <-- BV used by accident
}

Deducing the template<int> class ValueType argument of the Print class to a template class like the Value and BadValue classes enforces that all the template arguments in the parameter pack to the Print class are specializations of the same ValueType template class - this is intentional. That is, the second line in the main() function causes a compile-time error as the ValueType argument cannot be deduced to match both the Value and BadValue classes. If the user by accident tries to mix the templates when using the Print template a compile time error arises, which provides a bit of diagnostic.

The above implementation, however, still has the int type fixed for the inner template argument of the ValueType template template argument. How can I erase it and have it deduced as well?

Generally speaking, when deducing a template template argument, how can I access the inner template argument?


Solution

  • If I understand correctly, you want that Print<V2, V1, V2, VB>::print(); generate an error that is simpler to understand.

    For this, the best I can imagine is to works with static_assert()s.

    In this particular case -- Print is a struct with only a partial specialization implemented and no general version implemented -- a not really but simple solution is available: implement the general version to give a static_assert() error with a message of your choice.

    By example

    template <typename ... G>
    struct Print
     {
       static_assert( sizeof...(G) == 0, "not same int container for Print<>");
    
       static void print()
        { };
     };
    
    template <template<int> class ValueType, int ... Is>
    struct Print< ValueType<Is>... >
     {
       static void print()
        {
          using unused = int const [];
    
          (void)unused { (std::cout << Is, 0)... };
    
          std::cout << std::endl;
        }
     };
    

    Unfortunately this solution accept as valid Print<>; I don't know if is good for you.

    Another (better, IMHO, but more elaborate) solution can be transform the Print partial specialization in a specialization that accept variadic int containers (variadic ValueTypes instead a fixed ValueType) and, in a static_assert(), check (with a custom type traits) that all containers are the same.

    Bye example, with the following custom type traits

    template <template <int> class ...>
    struct sameCnts : public std::false_type
     { };
    
    template <template <int> class C0>
    struct sameCnts<C0> : public std::true_type
     { };
    
    template <template <int> class C0, template <int> class ... Cs>
    struct sameCnts<C0, C0, Cs...> : public sameCnts<C0, Cs...>
     { };
    

    you can write the Print specialization as follows

    template <template <int> class ... Cs, int ... Is>
    struct Print< Cs<Is>... >
     {
       static_assert(sameCnts<Cs...>{}, "different containers in Print<>");
    
       static void print()
        {
          using unused = int const [];
    
          (void)unused { (std::cout << Is, 0)... };
    
          std::cout << std::endl;
        }
     };
    

    If you can use C++17, you can use folding and the type traits can be written

    template <template <int> class, template <int> class>
    struct sameCnt : public std::false_type
     { };
    
    template <template <int> class C>
    struct sameCnt<C, C> : public std::true_type
     { };
    
    template <template <int> class C0, template <int> class ... Cs>
    struct sameCnts
       : public std::bool_constant<(sameCnt<C0, Cs>::value && ...)>
     { };
    

    and (using folding also in print() method) Print as follows

    template <template <int> class ... Cs, int ... Is>
    struct Print< Cs<Is>... >
     {
       static_assert( sameCnts<Cs...>{}, "different containers in Print<>");
    
       static void print()
        { (std::cout << ... << Is) << std::endl; }
     };
    

    -- EDIT --

    The OP ask

    But how can I have the Print class accept also, for example, types that are specialized for a double non-type value instead of the int non-type values?

    Not sure to understand what do you want but (remembering that a double value can't be a template non-type parameter) I suppose you want a Print that accept types with non-types template parameter when the type of this non type template parameter isn't fixed as in your example (int).

    For C++11 and C++14 I think that in necessary to explicit the type of the non type values.

    I mean... If you write Print as follows

    template <typename ...>
    struct Print;
    
    template <typename T, template <T> class ... Cs, T ... Is>
    struct Print< T, Cs<Is>... >
     {
       static_assert(sameCnts<Cs...>{}, "different containers in Print<>");
    
       // ...
     };
    

    you have to use it this way

    Print<int, V2, V1, V2, V3>::print();
    

    that is explicating int (or long, or whatever) as first template parameter. This because the int type can't be deduced.

    Starting from C++17 you can use auto as type for non-type template parameter, so you can write Print as follows

    template <typename ...>
    struct Print;
    
    template <template <auto> class ... Cs, auto ... Is>
    struct Print< Cs<Is>... >
     {
       static_assert( sameCnts<Cs...>{}, "different containers in Print<>");
    
       static void print()
        { (std::cout << ... << Is) << std::endl; }
     }; 
    

    and the is no need to explicit the type and you can write

    Print<V2, V1, V2, V3>::print();
    

    In this case, you have to use auto instead of int also in sameCnt and sameCnts.