Reputation: 2077
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;
}
Upvotes: 9
Views: 1519
Reputation: 1562
For those who want C++11 version without using fancy concepts or requires clause:
// Get the template arity for a given type C.
// MaxSize can be set by the user to avoid infinite search
template<template<typename...> class C, std::size_t MaxSize = 99>
struct template_arity;
// Get the default template arity for a given type C.
template<template<typename...> class C, std::size_t MaxSize = 99>
struct default_template_arity;
// Get mandatory template arity for a given type C
template<template<typename...> class C>
struct mandatory_template_arity;
https://godbolt.org/z/4n84r64eT
#include <functional>
#include <map>
#include <tuple>
#include <type_traits>
#include <vector>
template<typename...>
using void_t = void;
template<typename Tuple1, typename Tuple2> struct tuple_cat_type_impl;
template<typename... Ts, typename... Us> struct tuple_cat_type_impl<std::tuple<Ts...>, std::tuple<Us...>> { using type = std::tuple<Ts..., Us...>; };
template<typename Tuple1, typename Tuple2> using tuple_cat_t = typename tuple_cat_type_impl<Tuple1, Tuple2>::type;
template<typename T, std::size_t N> struct type_sequence_impl;
template<typename T> struct type_sequence_impl<T, 0> { using type = std::tuple<>; };
template<typename T> struct type_sequence_impl<T, 1> { using type = std::tuple<T>; };
template<typename T> struct type_sequence_impl<T, 2> { using type = std::tuple<T, T>; };
template<typename T, std::size_t N>
struct type_sequence_impl {
using type = tuple_cat_t<typename type_sequence_impl<T, (N / 2)>::type, typename type_sequence_impl<T, N - (N / 2)>::type>;
};
template<typename T, std::size_t N>
using type_sequence = typename type_sequence_impl<T, N>::type;
template<template<typename...> class C, typename ArgTuple, typename = void>
struct can_put_template : std::false_type {};
template<template<typename...> class C, typename... Args>
struct can_put_template<C, std::tuple<Args...>, void_t<C<Args...>>> : std::true_type {};
template<template<typename...> class C, std::size_t N, bool = can_put_template<C, type_sequence<int, N>>::value>
struct mandatory_template_arity_impl;
template<template<typename...> class C, std::size_t N> struct mandatory_template_arity_impl<C, N, true> : std::integral_constant<std::size_t, N> {};
template<template<typename...> class C, std::size_t N> struct mandatory_template_arity_impl<C, N, false> : mandatory_template_arity_impl<C, N + 1> {};
template<template<typename...> class C>
struct mandatory_template_arity : mandatory_template_arity_impl<C, 0> {};
template<template<typename...> class C, std::size_t Size, bool = can_put_template<C, type_sequence<int, Size>>::value /* true */>
struct template_arity_impl;
template<template<typename...> class C, std::size_t Size> struct template_arity_impl<C, Size, true> : std::integral_constant<std::size_t, Size> {};
template<template<typename...> class C, std::size_t Size> struct template_arity_impl<C, Size, false> : template_arity_impl<C, Size - 1> {};
template<template<typename...> class C> struct template_arity_impl<C, 0, false> : std::integral_constant<std::size_t, std::size_t(-1)> {};
template<template<typename...> class C, std::size_t MaxSize = 6>
struct template_arity : template_arity_impl<C, MaxSize>{};
template<template<typename...> class C, std::size_t MaxSize = 6>
struct default_template_arity
: std::integral_constant<std::size_t, template_arity<C>::value - mandatory_template_arity<C>::value> {};
template<typename = void>
struct cxx14_less {};
int main() {
static_assert(template_arity<std::vector>::value == 2, "");
static_assert(mandatory_template_arity<std::vector>::value == 1, "");
static_assert(template_arity<std::map>::value == 4, "");
static_assert(mandatory_template_arity<std::map>::value == 2, "");
static_assert(template_arity<cxx14_less>::value == 1, "");
static_assert(mandatory_template_arity<cxx14_less>::value == 0, "");
return 0;
}
Upvotes: -1
Reputation: 2623
All information that we'd want to know regarding the "count template arguments" problem:
Example:
template <class, class = foo, class...> bar;
1
2
yes
Much like in 康桓瑋's solution, we fill the template class (C
) with a list of types (it doesn't matter the type [EDIT: see Note!
below]) and check if it abides by C
's declaration (by using SFINAE/requires
), if not we keep incrementing the size of the list - However, in our case we'll increment up starting from an empty list. Once we've found a list big enough, then we'll know the number of non-default arguments.
Once we have a list to fill all the non-default arguments, we can put those into C
: using T = C<Ts...>
. C
will automatically fill the default arguments, which are now contained in T
. All we need to do is send it to a type trait to sizeof...
the arguments and we've got the minimum number of arguments.
Finally, we check if T
can take one more type argument: if it can, it's a variadic!
template <auto v>
struct tag_v
{
static constexpr auto value = v;
};
//////////////////////////////////////////////////////
template <class>
struct size_of_pack;
template <template <class...> class P, class ... Ts>
struct size_of_pack <P<Ts...>>
: tag_v<sizeof...(Ts)> {};
template <class P>
constexpr int size_of_pack_v = size_of_pack<P>::value;
//////////////////////////////////////////////////////
template <class>
struct is_variadic;
template <template <class...> class P, class ... Ts>
struct is_variadic <P<Ts...>>
: tag_v<requires {typename P<Ts..., int>;}> {};
template <class P>
constexpr bool is_variadic_v = is_variadic<P>::value;
//////////////////////////////////////////////////////
struct template_class_args_info
{
int n_non_default;
int min_args;
bool variadic;
};
template <template <class...> class C, class ... Ts>
constexpr auto get_template_class_args_info ()
{
// We can invoke C with any types.
// Here we choose "int"
if constexpr (requires {typename C<Ts...>;})
{
using T = C<Ts...>; // C auto-fills the default args
return template_class_args_info{
sizeof...(Ts),
size_of_pack_v<T>,
is_variadic_v<T>};
}
else
{
return get_template_class_args_info<C, Ts..., int>();
}
}
Pre-c++20
:
The number of non-default arguments calculated has some limitations when the type declaration uses SFINAE:
template <class T, class = typename T::force_fail>
class tricky1;
The answer to n_non_default
should be 1
, but when we try tricky1<int>
it's SFINAE-d, so we incorrectly provide 2
as the answer.
After c++20
:
concepts
actually makes this problem much more difficult, consider:
template <class T>
concept ForceFail = requires {typename T::force_fail;};
template <class T>
requires ForceFail<T>
class tricky2;
Similar to the previous example, we're restricting the type of the argument, but unlike last time we can't override it by adding another type, hence we can no longer fill class templates with just anything. Needless to say, get_template_class_args_info<trick2>()
fails to compile altogether!
Upvotes: 2
Reputation: 42736
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);
Upvotes: 2
Reputation: 275310
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
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.
Upvotes: 1
Reputation: 1
You can use a specific template instance.
template <typename T>
struct template_count { static constexpr unsigned value = 0; };
template <template<typename...> typename T, typename ... Args>
struct template_count<T<Args...>> { static constexpr unsigned value = sizeof...(Args); };
int main() {
static_assert(template_count<test<int,int,int>>::value == 3);
}
The general solution without the particular instantiation might have problems with variadic templates.
template <typename ... T> struct test {};
int main() {
static_assert(template_count<test>::value == ????); //no solution
static_assert(template_count<test<int,int>>::value == 2);
}
Upvotes: -1
Reputation: 2077
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.
Upvotes: 2