This uses value-based metaprogramming.
tag(_t) is a compile time value representing a type.
template<class T>
struct tag_t { using type=T; };
template<class T, T t>
struct tag_t<std::integral_constant<T,t>>:std::integral_constant<T,t> { using type=T; };
template<class T>
constexpr tag_t<T> tag = {};
value(_t) is a compile-time value (and a tag).
template<auto x>
using value_t = tag_t<std::integral_constant<std::decay_t<decltype(x)>, x>>;
template<auto x>
constexpr value_t<x> value = {};
ztemplate(_t)<Z> is a compile time value representing a template.
template<template<class...>class Z, std::size_t N=0>
struct ztemplate_type : value_t<N>, ztemplate_type<Z, static_cast<std::size_t>(-1)> {};
template<template<class...>class Z>
struct ztemplate_type<Z, static_cast<std::size_t>(-1)>:
tag_t<ztemplate_type<Z, static_cast<std::size_t>(-1)>>
{
template<class...Ts>
constexpr tag_t<Z<Ts...>> operator()( tag_t<Ts>... ) const { return {}; }
};
template<template<class>class Z>
constexpr ztemplate_type<Z,1> ztemplate_map( ztemplate_type<Z>, int ) { return {}; }
template<template<class,class>class Z>
constexpr ztemplate_type<Z,2> ztemplate_map( ztemplate_type<Z>, int ) { return {}; }
template<template<class,class,class>class Z>
constexpr ztemplate_type<Z,3> ztemplate_map( ztemplate_type<Z>, int ) { return {}; }
template<template<class...>class Z>
constexpr ztemplate_type<Z> ztemplate_map( ztemplate_type<Z>, ... ) { return {}; }
template<template<class...>class Z>
using ztemplate_t = decltype( ztemplate_map( ztemplate_type<Z>{}, true ) );
template<template<class...>class Z>
constexpr ztemplate_t<Z> ztemplate = {};
don't use ztemplate_type directly; use ztemplate_t and ztemplate.
Test code:
template<class> struct A {};
template<class,class> struct B {};
template<class,class,class> struct C {};
template<class,class,class,class> struct D {};
auto a = ztemplate<A>;
auto b = ztemplate<B>;
auto c = ztemplate<C>;
auto d = ztemplate<D>;
(void)a, (void)b, (void)c, (void)d;
auto a_v = a(tag<int>);
auto b_v = b(tag<int>, tag<double>);
auto c_v = c(tag<int>, tag<double>, tag<char>);
auto d_v = d(tag<int>, tag<double>, tag<char>, tag<void>);
(void)a_v, (void)b_v, (void)c_v, (void)d_v;
std::cout << a << b << c << "\n";
output is:
123
Live example.
In this paradigm, a ztemplate is a function object that maps tags. Those tags could be ztemplates or values or wrap raw types.