获取第一个模板参数的稳健方法

Robust method of getting the first template parameter

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

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> >'

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

最后,问题是:我可以一般地 "ignore" 任何我不需要的模板参数吗? "generically" 我的意思是忽略值和类型。

没有

作为一般规则,使用任意模板使其参数有意义的方式不是一个好主意。这只是您将 运行 遇到的众多问题之一。

模板参数是位置性的,但对于每个位置并没有普遍认同的含义。容器的第一个参数往往是值类型,但即使是关联容器也不是这样(前两个参数被合成为值类型)。

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>>intcontainer_first_type<std::array<double, 7>>double

Live example

无法处理每个案例。

您可以采取的解决方法:

  • 在 class

    中提供 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.