如何在编译时推断出嵌套 std::vector 的内部类型?

How can I deduce the inner type of a nested std::vector at compile time?

前几天我非常 问了一个关于嵌套向量的问题,但我遇到了另一个让我难过的问题。我需要在编译时获取嵌套向量的最内层类型,以便我可以使用它作为模板参数传递。

例如,如果我有这个嵌套向量:

std::vector<std::vector<std::vector<int>>> v;

我需要一种提取 int 的方法,这样我就可以调用一个采用嵌套向量并处理元素的函数,如下所示:

foo<int>(v);

除了要注意的是,此函数应该能够处理包含任何类型的任何深度的嵌套向量。当我调用 foo 时,我希望自动为我推导出内部类型。

所以调用可能看起来像这样:

foo<inner_type_t<v>>(v);

其中 inner_type_t 是某种形式的递归模板,在给定 v.

时解析为 int

我认为解决方案与另一个问题的解决方案类似,但我一直无法解决...在递归模板方面我还是个新手。

编辑:

这是我目前所掌握的...

template <typename T>
struct inner_type
{
    using type = T;
};

template <typename T>
struct inner_type<std::vector<T>>
{
    using type = inner_type<T>;
};

int main()
{
    std::vector<std::vector<int>> v  = {
        { 1, 2}, {3, 4}
    };

    std::cout << typeid(inner_type<decltype(v)>::type).name();
}

输出:

struct inner_type<class std::vector<int,class std::allocator<int> > >

哇,我真的很接近哈哈,成功了!

我只需要稍微更改模板专业化即可正确地递归获取类型。

template <typename T>
struct inner_type
{
    using type = T;
};

template <typename T>
struct inner_type<std::vector<T>>
{
    // Had to change this line
    using type = typename inner_type<T>::type;
};

int main()
{
    std::vector<std::vector<int>> v  = {
        { 1, 2}, {3, 4}
    };

    std::cout << typeid(inner_type<decltype(v)>::type).name();
}

输出:

int

您可以定义以下主要 class 模板,inner_type:

template<typename T>
struct inner_type {
   using type = T;
};

用作基本情况,即用于停止递归——当模板参数不匹配时std::vector<T>(见下文)。

然后,以下方便的别名模板仅用于编写类似 C++14 的尾随 _t 而不是 ::type

template<typename T>
using inner_type_t = typename inner_type<T>::type;

最后,std::vector<T> 的特化 – 递归情况:

template<typename T>
struct inner_type<std::vector<T>> {
   using type = inner_type_t<T>;
};

当传递 std::vector<T> 作为模板参数时,此特化匹配。否则,将匹配第一个(见上文)。


检查一下。您可以声明以下 class 模板:

template<typename> struct type_shower;

然后:

auto main() -> int {
   using type = inner_type_t<std::vector<std::vector<int>>>;
   type_shower<type> _;
}

它应该显示一个错误,指出未定义模板的隐式实例化 type_shower<int>。这意味着 typeint.

遵循 Bulletmagnet 建议使用 value_type 成员类型的解决方案:

template<class T, typename = void>
struct inner_type {
    using type = T;
};

template<class T>
struct inner_type<T, std::void_t<typename T::value_type>>
    : inner_type<typename T::value_type> {};

template<class T>
using inner_type_t = typename inner_type<T>::type;

using VV = std::vector<std::vector<int>>;
static_assert(std::is_same_v<inner_type_t<VV>, int>);

可以在 this question 中找到关于 std::void_t 工作原理的非常好的解释。如果 typename T::value_type 格式不正确,它在这里用于静默拒绝特化。

@tjwrona1992 的解决方案没问题,但不允许向量具有不同的分配器。此外,让我们使用特征的 _t 版本使这个 C++14 友好。

这应该可以解决问题:

template <typename T> struct inner_type { using type = T; };

template<class T, class Alloc>
struct inner_type<std::vector<T, Alloc>> { using type = typename inner_type<T>::type; };

template<class T>
using inner_type_t = typename inner_type<T>::type;

此外,对于类型名称,您应该使用为 C++17 here for C++14 or here 实现的 type_name() 函数。

See it working live...

这是一个更通用的解决方案。它也适用于 listforward_list,而不仅仅是 vector

#include <vector>
#include <list>
#include <forward_list>
#include <type_traits>

//using nested = std::vector<std::vector<std::vector<int>>>;
using nested = std::list<std::vector<std::forward_list<double>>>;

// primary template handles types that have no nested value_type member:
template< class, class = std::void_t<> >
struct has_vt : std::false_type { };

// specialization recognizes types that do have a nested value_type member:
template< class T >
struct has_vt<T, std::void_t<typename T::value_type>> : std::true_type { };

template <typename T, typename Enable = void> struct inner;

template <typename T> struct inner<T, typename std::enable_if<!has_vt<T>::value>::type> {
    using vt = T;
};

template <typename T> struct inner<T, typename std::enable_if<has_vt<T>::value>::type> {
    using vt = typename inner<typename T::value_type>::vt;
};

template<typename> struct type_shower;

int main() {
    using deeep = inner<nested>::vt;
    type_shower<deeep> _;
    return 0;
}

Boost.Mp11,这是一个简短的单行(一如既往)

template <typename T>
using inner_value_t = mp_last<mp_iterate<T, mp_identity_t, mp_first>>;

Demo.

这依赖于你只关心vector,而vector<T, A>的值类型只是T。这实际上适用于所有序列容器(这适用于 listdequeforward_list 等,很好。虽然对于 vector<map<int, double>> 它会给你 int).

如果我们有 vector<vector<char>>mp_iterate 将首先生成序列 mp_list<vector<vector<char>>, vector<char>, char>(应用 mp_first 直到我们不能再应用,然后将结果传递给 mp_identity_t 这只是一个空操作)。然后 mp_last returns 该列表中的最后一个类型。这是我们想要的类型:char.


如果您想扩展以支持任意范围,您可以使用 std::ranges::range_value_t (C++20) 而不是 mp_first,这是最通用的解决方案。

或者,如果您只关心嵌套的 value_type 别名:

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

template <typename T>
using inner_value_t = mp_last<mp_iterate<T, mp_identity_t, value_type_t>>;