代码之家  ›  专栏  ›  技术社区  ›  Not a real meerkat

获取第一模板参数的鲁棒方法

  •  2
  • Not a real meerkat  · 技术社区  · 6 年前

    假设模板具有一个只包含类型的模板参数列表,则以下类型特征可以提取第一个类型参数:

    template<typename T>
    struct front {};
    
    template<template<typename...> typename C, typename FirstT, typename... Args>
    struct front<C<FirstT, Args...>> {using type = FirstT;};
    
    template<typename T>
    using front_t = typename front<T>::type;
    
    template<typename...> struct foo{};
    
    using std::is_same_v;
    static_assert(is_same_v<front_t<foo<int, double>>, int>); // Ok
    static_assert(is_same_v<front_t<foo<int, double>>, double>); // Fail (as expected)
    

    但是,这不适用于具有值参数的模板:

    using std::array;
    static_assert(is_same_v<front_t<array<int, 5>>, int>);
    // error: no type named 'type' in 'struct front<std::array<int, 5> >'
    

    好的,现在我还需要考虑任何值参数:

    template<typename T>
    struct front {};
    
    // First parameter is a type, other parameters are types
    template<template<typename...> typename C, typename FirstT, typename... Args>
    struct front<C<FirstT, Args...>> {using type = FirstT;};
    
    // First parameter is a type, other parameters are values
    template<template<typename, auto...> typename C, typename FirstT, auto... Args>
    struct front<C<FirstT, Args...>> {using type = FirstT;};
    
    // First parameter is a value, other parameters are types
    template<template<auto, typename...> typename C, auto FirstA, typename... Args>
    struct front<C<FirstA, Args...>> {constexpr static const auto value = FirstA;};
    
    // First parameter is a value, other parameters are values
    template<template<auto...> typename C, auto FirstA, auto... Args>
    struct front<C<FirstA, Args...>> {constexpr static const auto value = FirstA;};
    
    // Avoid ambiguity if there's only a single type parameter
    template<template<typename...> typename C, typename FirstT>
    struct front<C<FirstT>> {using type = FirstT;};
    
    // Avoid ambiguity if there's only a single value parameter
    template<template<auto...> typename C, auto FirstA>
    struct front<C<FirstA>> {constexpr static const auto value = FirstA;};
    
    template<typename T>
    using front_t = typename front<T>::type;
    
    template<typename T>
    const auto front_v = front<T>::value;
    
    template<typename...> struct foo{};
    template<auto...> struct bar{};
    
    static_assert(std::is_same_v<front_t<foo<int>>, int>); // Ok
    static_assert(std::is_same_v<front_t<foo<int, double>>, double>); // Fail (as expected)
    static_assert(std::is_same_v<front_t<std::array<int, 5>>, int>); // Ok
    static_assert(front_v<bar<5, 4>> == 5); // Ok
    static_assert(front_v<bar<5, 4>> == 4); // Fail (as expected)
    

    但是我尝试更多的混音:

    template<typename, typename, auto...> struct baz{};
    
    static_assert(std::is_same_v<front_t<baz<int, int>>, int>);
    // error: no type named 'type' in 'struct front<baz<int, int> >'
    

    这显然是失控了。现在,我不仅要担心类型与值参数的混合,还要担心这些参数的顺序,并为它们的每一个组合编写一个专门化。但我只想要这些参数中的第一个!其他的根本不重要。

    最后,问题是:我能一般地“忽略”任何我不需要的模板参数吗?一般来说,我指的是忽略值和类型。

    3 回复  |  直到 6 年前
        1
  •  5
  •   Yakk - Adam Nevraumont    6 年前

    不。

    作为一般规则,使用任意模板使其参数有意义的方式是一个坏主意。这只是你会遇到的众多问题之一。

    模板参数是位置性的,但是对于每一个位置都没有普遍同意的意义。容器的第一个参数往往是值类型,但对于关联容器(前两个参数被合成为值类型)来说,这是不正确的。

    template<class T>
    using value_type = typename T::value_type;
    

    有很多箱子。如果我们想要获得辅助容器上的密钥类型,我们需要:

    template<class T>
    using key_type = typename T::key_type;
    
    namespace details {
      template<class...>using void_t=void;
      template<template<class...>class Z, class, class...Ts>
      struct can_apply:std::false_type {};
      template<template<class...>class Z, class...Ts>
      struct can_apply<Z,void_t<Z<Ts...>>, Ts...>:std::true_type {};
    }
    template<template<class...>class Z, class...Ts>
    using can_apply = details::can_apply<Z,void,Ts...>;
    
    
    template<template<class>class Z>
    struct ztemplate1 {
      template<class T>
      using result = Z<T>;
    };
    
    template<bool b, template<class>class True, template<class> class False, class T>
    using conditional_apply1 = typename std::conditional_t<
      b,
      ztemplate1<True>,
      ztemplate1<False>
    >::template result<T>;
    
    template<class X>
    using container_first_type = conditional_apply1<
      can_apply<key_type, X>{},
      key_type,
      value_type,
      X
    >;
    

    现在 container_first_type<std::map<std::string, int>> std::string ,同时 container_first_type<std::vector<int>> int container_first_type<std::array<double, 7>> double .

    Live example

        2
  •  0
  •   Jarod42    6 年前

    没有办法处理每一个案件。

    您可以做的工作是:

    • 在类内提供typedef

      template <typename T /*, */> class Foo {
          using first_type = T;
      }
      
    • 只使用类型,并且可能 std::integral_constant

      Bar<int, std::true_type, std::integral_constant<int, 42>> bar;
      
    • 只处理常见情况 array .

        3
  •  0
  •   Not a real meerkat    6 年前

    雅克的答案是对点(一如既往),但实现 container_first_type 似乎过于复杂。以下是一个更简单的版本:

    template<typename, typename = std::void_t<>>
    struct optional_key_type_impl {using type=void;};
    
    template<typename T>
    struct optional_key_type_impl<T, std::void_t<typename T::key_type>> {using type=typename T::key_type;};
    
    template<typename T>
    using optional_key_type = typename optional_key_type_impl<T>::type;
    
    // This could be variadic and support multiple types, but there's really no need.
    template<typename T, typename> struct select_impl{ using type=T; };
    template<typename T> struct select_impl<void, T> { using type=T; };
    template<typename T1, typename T2> using select_t = typename select_impl<T1, T2>::type;
    
    template<typename T>
    using container_first_type = select_t<optional_key_type<T>, typename T::value_type>;