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?
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
.