Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Counting parameters of a template template type

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;
}
like image 710
moisrex Avatar asked Aug 13 '20 16:08

moisrex


3 Answers

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.

like image 146
康桓瑋 Avatar answered Nov 01 '22 06:11

康桓瑋


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.

like image 1
moisrex Avatar answered Nov 01 '22 07:11

moisrex


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.

like image 1
Yakk - Adam Nevraumont Avatar answered Nov 01 '22 07:11

Yakk - Adam Nevraumont