Following code works in GCC (at least in GCC 10.1.0), but not in MSVC and Clang. I'm not sure if it's legal in C++ standard.
I'm trying to count the parameters in a template template
type.
Is the following code a valid C++ code, if yes, then how to make them work in Clang and MSVC, if not, is there an alternative to this?
Code on compiler explorer
template <template<typename...> typename T>
struct template_count {
static constexpr unsigned value = 0;
};
template <template<typename> typename T>
struct template_count<T> {
static constexpr unsigned value = 1;
};
template <template<typename, typename> typename T>
struct template_count<T> {
static constexpr unsigned value = 2;
};
template <template<typename, typename, typename> typename T>
struct template_count<T> {
static constexpr unsigned value = 3;
};
template <typename one, typename two, typename three>
struct test {
};
int main() {
return template_count<test>::value;
}
For variadic templates such as std:tuple
, it is impossible to calculate the number of types it can accept, but once we set a maximum count number such as 50
, it will become relatively easy.
The basic idea is to try to instantiate the template template parameters from the maximum number of types (here we just use void
) and decrease one by one until it succeeds.
#include <utility>
template<std::size_t>
using void_t = void;
template<template<class...> class C, std::size_t... Is>
constexpr std::size_t template_count_impl(std::index_sequence<Is...>) {
constexpr auto n = sizeof...(Is);
if constexpr (requires { typename C<void_t<Is>...>; })
return n;
else
return template_count_impl<C>(std::make_index_sequence<n - 1>{});
}
template<template<class...> class C>
constexpr std::size_t template_count() {
constexpr std::size_t max_count = 50;
return template_count_impl<C>(std::make_index_sequence<max_count>{});
}
template<class> struct A {};
template<class,class=int> struct B {};
template<class,class,class> struct C {};
template<class,class,class,class> struct D {};
template<class,class,class,class,class=int> struct E {};
static_assert(template_count<A>() == 1);
static_assert(template_count<B>() == 2);
static_assert(template_count<C>() == 3);
static_assert(template_count<D>() == 4);
static_assert(template_count<E>() == 5);
Demo.
After a hint from someone in twitter, I found a better solution that works on GCC and Clang (Link to compiler explorer) (in github):
#include <type_traits>
#include <cstddef>
struct variadic_tag {};
struct fixed_tag : variadic_tag {};
struct number_signature {};
template<std::size_t V>
struct ParameterNumber : number_signature {
constexpr static auto value = V;
};
template<typename T>
concept NumberObjConcept = std::is_base_of_v<number_signature, T>;
template<template<typename> typename>
auto DeduceArgs() -> ParameterNumber<1>;
template<template<typename, typename> typename>
auto DeduceArgs() -> ParameterNumber<2>;
template<template<typename, typename, typename> typename>
auto DeduceArgs() -> ParameterNumber<3>;
template<template<typename, typename, typename, typename> typename>
auto DeduceArgs() -> ParameterNumber<4>;
template<template<typename, typename, typename, typename, typename> typename>
auto DeduceArgs() -> ParameterNumber<5>;
template<template<typename, typename, typename, typename, typename, typename> typename>
auto DeduceArgs() -> ParameterNumber<6>;
template<template<typename, typename, typename, typename, typename, typename, typename> typename>
auto DeduceArgs() -> ParameterNumber<7>;
template<template<typename, typename, typename, typename, typename, typename, typename, typename> typename>
auto DeduceArgs() -> ParameterNumber<8>;
template<template<typename, typename, typename, typename, typename, typename, typename, typename, typename> typename>
auto DeduceArgs() -> ParameterNumber<9>;
template<template<typename, typename, typename, typename, typename, typename, typename, typename, typename, typename> typename>
auto DeduceArgs() -> ParameterNumber<10>;
template<template<typename...> typename F>
auto DeduceTemplateArgs(variadic_tag) -> ParameterNumber<1000000000>; // a meaningless big number
template<template<typename...> typename F>
auto DeduceTemplateArgs(fixed_tag) -> decltype(DeduceArgs<F>());
template <typename one, typename two, typename three>
struct test {
};
#define __DEDUCE_TEMPLATE_ARGS(c) decltype(DeduceTemplateArgs<c>(fixed_tag{}))
int main() {
return __DEDUCE_TEMPLATE_ARGS(test)::value;
}
of course the above code fails on such situations:
template <template<typename> typename>
struct test {};
and that's fine for most situations.
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 tag
s. Those tag
s could be ztemplate
s or value
s or wrap raw types.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With