以下代码适用于 GCC(至少在 GCC 10.1.0 中),但不适用于 MSVC 和 Clang。我不确定它在 C++ 标准中是否合法。
我正在尝试计算
template template
类型中的参数。
以下代码是有效的 C++ 代码吗?如果是,那么如何使它们在 Clang 和 MSVC 中工作,如果不是,是否有替代方案?
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;
}
twitter 中某人的提示后,我找到了一个适用于 GCC 和 Clang 的更好的解决方案(链接到编译器资源管理器)(在 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;
}
当然,上面的代码在这种情况下会失败:
template <template<typename> typename>
struct test {};
这对于大多数情况来说都很好。
std:tuple
这样的可变参数模板,无法计算它可以接受的类型数量,但是一旦我们设置了像
50
这样的最大计数数量,就会变得相对容易。基本思想是尝试从最大数量的类型(这里我们只使用
void
)开始实例化模板模板参数,并逐一减少,直至成功。
#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);
“计算模板参数” 问题的所有信息:
template <class, class = foo, class...> bar;
1
2
yes
康桓玮的解决方案,我们用类型列表填充模板类(C
)(类型并不重要[编辑:参见下面的
Note!
])并检查是否它遵守
C
的声明(通过使用 SFINAE/
requires
),如果没有,我们会继续增加列表的大小 - 但是,在我们的例子中,我们将从空列表开始递增。一旦我们找到足够大的列表,我们就会知道非默认参数的数量。
C
:
using T = C<Ts...>
。
C
将自动填充默认参数,这些参数现在包含在
T
中。我们需要做的就是将其发送到类型特征到
sizeof...
参数,这样我们就得到了最小数量的参数。
T
是否可以再接受一个类型参数:如果可以,那么它是一个可变参数!
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>();
}
}
演示:c++20、c++11
c++20
:当类型声明使用 SFINAE 时,计算的非默认参数的数量有一些限制:
template <class T, class = typename T::force_fail>
class tricky1;
n_non_default
的答案应该是
1
,但是当我们尝试
tricky1<int>
时,它是SFINAE-d,所以我们错误地提供了
2
作为答案。
c++20
之后:
concepts
实际上使这个问题更加困难,考虑:
template <class T>
concept ForceFail = requires {typename T::force_fail;};
template <class T>
requires ForceFail<T>
class tricky2;
与前面的示例类似,我们限制了参数的类型,但与上次不同的是,我们不能通过添加其他类型来覆盖它,因此我们不能再用任何东西填充类模板。不用说,get_template_class_args_info<trick2>()
完全无法编译!
tag
(
_t
) 是表示类型的编译时值。
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
) 是编译时值(和标签)。
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>
是代表模板的编译时值。
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 = {};
不要直接使用ztemplate_type
;使用
ztemplate_t
和
ztemplate
。测试代码:
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";
输出为:
123
实例.
在此范例中,ztemplate
是映射
tag
的函数对象。这些
tag
可以是
ztemplate
或
value
或包装原始类型。
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);
}
没有特定实例化的通用解决方案可能会出现可变参数模板的问题。
template <typename ... T> struct test {};
int main() {
static_assert(template_count<test>::value == ????); //no solution
static_assert(template_count<test<int,int>>::value == 2);
}
// 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;
}