2

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?

Kamajii
  • 1,826
  • 9
  • 21

2 Answers2

1

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.

max66
  • 65,235
  • 10
  • 71
  • 111
  • Maybe it would be good to explain what was going wrong in the original post: That without the variadic template template parameter it was being deduced as two conflicting templates. – Tim Seguine Jan 28 '18 at 19:43
  • @TimSeguine - I've tried to explain this but I'm not good with words... I'll try to explain better. – max66 Jan 28 '18 at 19:45
  • Actually, I exactly WANT it to go wrong with conflicting templates. I suppose my wording wasn't that clear either :-) The problem is I want to have the `int` deduced. – Kamajii Jan 29 '18 at 18:37
  • @Kamajii - ehmm... yes, I'm really confused now; do you want an error but do you want the `int` deduced? Suggestion: explain what do you exactly want, with examples, but do it modifying the question (so other readers can notice it). – max66 Jan 29 '18 at 19:01
  • Yes, exactly. The `Print` template should enforce that all its template arguments have the same (template) type. This is why I don't want the template itself be part of the parameter pack. – Kamajii Jan 29 '18 at 19:30
  • @Kamajii - so what's wrong with the error in your original code? – max66 Jan 29 '18 at 19:42
  • Aaaaahhhhh. I'm sorry. Nothing is wrong with it, it is _intended_ - amended! – Kamajii Jan 29 '18 at 20:11
  • @Kamajii - sorry but I don't understand what do you want obtain; please, can you modify your question to explain it? – max66 Jan 29 '18 at 20:14
  • @Kamajii - so do you want the error but only a better diagnostic? Another doubt: do you want to accept `ValueType`s different than `Value`; in other words: `Print::print();` should compile or should give an error (with better diagnostic)? – max66 Jan 29 '18 at 20:29
  • @Kamajii - answer modified in the case you want accept valid `Print::print()` – max66 Jan 30 '18 at 02:06
  • I like the way you implemented the diagnostics. 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? – Kamajii Jan 30 '18 at 20:26
  • @Kamanji - uhmmm... a `double` can't be a non-type template value; anyway, do you mean that the `int` in `template class ValueType` should be a template parameter? – max66 Jan 30 '18 at 21:05
  • @max66: Yes, of course - with `double` non-type value I mean concrete instances of `double`, i.e. numbers. It is a little frustrating that my English capabilities don't seem to suffice to get the wording unambiguous. I probably need to sort my vocabulary and coin the correct terms :-/ – Kamajii Jan 31 '18 at 16:10
  • @Kamajii - the frustrating part (for me) is that my English capabilities are worse than yours. Anyway... answer improved; hope this helps. – max66 Jan 31 '18 at 18:02
0

If you work in C++17, you can declare non-type template parameter with auto, so simply declare Is as auto..., and use auto instead of int in the function definition as possible as you can.

Of course, since type of elements of Is may be different, it may be impossible to declare the array is. Instead, you can use std::tuple and print the tuple instead.

// print_tuple is used to print a tuple

template<std::size_t I = 0, typename... Tp>
inline typename std::enable_if<I == sizeof...(Tp)>::type
  print_tuple(const std::tuple<Tp...>&)
  { }

template<std::size_t I = 0, typename... Tp>
inline typename std::enable_if<I < sizeof...(Tp)>::type
  print_tuple(const std::tuple<Tp...>& t)
  {
    std::cout << std::get<I>(t);  
    print_tuple<I + 1, Tp...>(t);
  }

// ...

template<template<int> class ValueType, auto... Is>
                                     // ^^^^
struct Print< ValueType<Is>... > {
    static void print() {
        print_tuple(std::make_tuple(Is...)); // make a tuple, and print it
    }
};

LIVE EXAMPLE

The above pattern (making a tuple then dealing with the tuple) allows you to apply some complicated function to the parameter pack Is. However, if you only want to print the pack, you can alternatively use the C++17 feature fold expression instead, which is simpler.

template<template<int> class ValueType, auto... Is>
                                     // ^^^^
struct Print< ValueType<Is>... > {
    static void print() {
        (std::cout << ... << Is); // fold expression, also C++17 feature
    }
};

LIVE EXAMPLE

xskxzr
  • 12,442
  • 12
  • 37
  • 77