【问题标题】:How can I deduce the inner type of a nested std::vector at compile time?如何在编译时推断嵌套 std::vector 的内部类型?
【发布时间】:2020-04-17 14:10:00
【问题描述】:

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

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

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

【问题讨论】:

  • 到目前为止你尝试过什么?大多数whatever_t 类型是通过使用成员typetemplate &lt;typename T&gt; using whatever_t = typename whatever&lt;T&gt;::type; 创建结构whatever 来创建的。
  • 我在尝试解决问题时添加了问题。
  • 另一种选择是跟随vector的value_type,如果没有value_type则停止。

标签: c++ templates c++14 template-meta-programming


【解决方案1】:

哇,我真的很接近哈哈,开始工作了!

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

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

【讨论】:

    【解决方案2】:

    @tjwrona1992 的解决方案还可以,但不允许使用具有不同分配器的向量。另外,让我们使用 _t 版本的 trait 使这个 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;
    

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

    See it working live...

    【讨论】:

      【解决方案3】:

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

      std::void_t 工作原理的一个很好的解释可以在this question 中找到。如果typename T::value_type 格式不正确,则在这里使用它来默默地拒绝专业化。

      【讨论】:

      • 您能解释一下std::void_t 的工作原理吗?我可以跟着它除了那个大声笑
      • @tjwrona1992,请参阅 this question 以获得很好的解释。 std::void_t&lt;typename T::value_type&gt; 使专业化不正确。然后,由于 SFINAE,它被默默地拒绝了。
      【解决方案4】:

      您可以定义以下主要类模板inner_type

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

      用作 基本情况,即用于停止递归 - 当模板参数与 std::vector&lt;T&gt; 不匹配时(见下文)。

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

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

      最后,std::vector&lt;T&gt; 的特化——递归案例

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

      在传递 std::vector&lt;T&gt; 作为模板参数时匹配此特化。否则,将匹配第一个(见上文)。


      检查它。您可以声明以下类模板:

      template<typename> struct type_shower;
      

      然后:

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

      它应该显示一个错误,说明未定义模板的隐式实例化type_shower&lt;int&gt;。这意味着typeint

      【讨论】:

      • 这看起来是一个有趣的技巧,但是当我尝试它时,我得到了一个不同的错误,而不是显示类型:( error C2079: '_' uses undefined struct 'type_shower&lt;type&gt;'。可能是因为我使用了不同的编译器。
      • @tjwrona1992 我明白了。如果你把std::vector&lt;std::vector&lt;int&gt;&gt;直接写成type_shower&lt;&gt;的模板参数呢???
      • 是的,它会吐出一个类型,但它很丑,哈哈error C2079: '_' uses undefined struct 'type_shower&lt;std::vector&lt;std::vector&lt;int,std::allocator&lt;_Ty&gt;&gt;,std::allocator&lt;std::vector&lt;_Ty,std::allocator&lt;_Ty&gt;&gt;&gt;&gt;&gt;'
      【解决方案5】:

      这是一个更通用的解决方案。它也适用于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;
      }
      

      【讨论】:

        【解决方案6】:

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

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

        Demo.

        这取决于你只关心vector,而vector&lt;T, A&gt;的值类型只是T。这实际上适用于所有序列容器(这适用于listdequeforward_list 等,很好。虽然对于vector&lt;map&lt;int, double&gt;&gt;,它会给你int)。

        如果我们有vector&lt;vector&lt;char&gt;&gt;mp_iterate 将首先产生序列mp_list&lt;vector&lt;vector&lt;char&gt;&gt;, vector&lt;char&gt;, char&gt;(应用mp_first 直到我们不能再使用,然后将结果传递给mp_identity_t,这只是一个无操作)。然后mp_last 返回该列表中的最后一个类型。我们想要的类型是: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>>;
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2022-08-05
          • 1970-01-01
          • 2022-12-07
          • 2021-05-06
          • 2013-11-10
          • 1970-01-01
          • 1970-01-01
          • 2019-11-17
          相关资源
          最近更新 更多