计算模板模板类型的参数

问题描述 投票:0回答:6

以下代码适用于 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; }
    
c++ templates metaprogramming c++20
6个回答
2
投票
经过

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 {};
这对于大多数情况来说都很好。


2
投票
对于像

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);

演示。


2
投票
有什么问题吗?

我们想了解的有关

“计算模板参数” 问题的所有信息:

    有多少个非默认参数?
  1. 有多少非可变参数(即最小参数)?
  2. 它是可变的吗?

示例:

template <class, class = foo, class...> bar;

    非默认参数 =
  1. 1
    
    
  2. 最小参数(非可变参数)=
  3. 2
    
    
  4. 是可变参数 =
  5. yes
    
    

解决方案(想法)

  1. 很像

    康桓玮解决方案,我们用类型列表填充模板类(C

    )(类型并不重要[编辑:参见下面的
    Note!
    ])并检查是否它遵守 
    C
     的声明(通过使用 SFINAE/
    requires
    ),如果没有,我们会继续增加列表的大小 - 但是,在我们的例子中,我们将从空列表开始递增。一旦我们找到足够大的列表,我们就会知道非默认参数的数量。

  2. 一旦我们有了一个列表来填充所有非默认参数,我们就可以将它们放入

    C

    using T = C<Ts...>
    C
     将自动填充默认参数,这些参数现在包含在 
    T
     中。我们需要做的就是将其发送到类型特征到 
    sizeof...
     参数,这样我们就得到了最小数量的参数。

  3. 最后,我们检查

    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++20c++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>()

完全无法编译!


1
投票
这使用基于值的元编程。

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
 或包装原始类型。


-1
投票
您可以使用特定的模板实例。

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); }
    

-1
投票
对于那些想要 C++11 版本而不使用花哨概念或 require 子句的人:

// 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; }
    
© www.soinside.com 2019 - 2024. All rights reserved.