【问题标题】:How do I define a vector of vectors of different types using a variadic template?如何使用可变参数模板定义不同类型向量的向量?
【发布时间】:2018-10-08 15:22:54
【问题描述】:

我想制作一个包含不同类型向量的向量。假设我有 4 节课;狗,猫,猪,牛。我想要一个包含每个向量的向量,并且能够通过两个索引访问它们,这样我就可以迭代它们,如果它是向量的向量就是这种情况。

我一直在玩弄这样的事情:

std::vector<std::variant<std::vector<Dog>,
    std::vector<Cat>,
    std::vector<Pig>,
    std::vector<Cow>>>;

此外,我希望能够使用可变参数模板构造来构造这些数组,这样我就可以轻松地制作另一个向量向量,例如 Apple、Pear、Orange、Lemon、Grape、Cherry。

我希望能够在我的代码中编写如下内容:

MyVectorOfVectors<Dog,Cat,Pig,Cow> animals;
MyVectorOfVectors<Apple, Pear, Orange, Lemon, Grape, Cherry> fruits;

并为每种类型制作向量并将这些向量存储在另一个向量(或类似的)中,我可以通过索引访问该向量。显然,这个向量需要是某种异构容器,如上面所建议的,带有变体向量。我想这必须包含在某种可变参数模板类定义中。

所以访问数组中的第三个 Dog 需要类似这样的功能

Dog mydog = animals[0][3];

或者如果解决方案必须封装在一个类中,

Dog mydog = animals.thearray[0][3];

我意识到这可以使用类层次结构、动态分配和指向对象的指针来实现,但我正在寻找一种具有平面内存模型的解决方案以提高性能。

【问题讨论】:

    标签: c++ templates vector c++17


    【解决方案1】:
    template<typename... T>
    using MyVectorOfVectors = std::tuple<std::vector<T>...>;
    
    MyVectorOfVectors<Dog,Cat,Pig,Cow> animals;
    MyVectorOfVectors<Apple, Pear, Orange, Lemon, Grape, Cherry> fruits;
    
    void foo()
    {
        std::vector<Dog>& dogs = std::get<0>(animals);
        std::vector<Orange>& oranges = std::get<2>(fruits);
    }
    

    Demo

    您必须做出决定:您可以在编译时推断类型(在这种情况下,您对MyVectorOfVectors 的索引也必须在编译时知道) - 然后您将获得所有类型安全(如上所述)。

    如果您的索引也可以是运行时值,那么您需要一种类型擦除形式。这将伴随您说要避免的运行时开销。

    在任何情况下,您都不会得到Dog dog = animals[0][3],因为operator[] 的参数最终在编译时不一定是已知的MyVectorOfVectors(至少从编译器的角度来看)。

    【讨论】:

    • 这看起来很有希望。你知道迭代类型的方法吗?我当然不能使用 for 循环,因为迭代器将是运行时的。你能建议一种编写一段代码的方法来迭代动物和水果的类型吗?
    • 您可能会使用模板化遍历函数,类似于 visit 处理变体的方式。
    • 我将其标记为解决方案,因为我使用了此构造。它不能解决迭代问题,但我也有一些代码可以解决这个问题。 (见下文)。
    【解决方案2】:

    感谢您的建议。我已经使用 Max Langhof 的答案来生成数据结构并想出了一种进行迭代的方法。

    这是我最终得到的代码。

    #include <iostream>
    #include <tuple>
    #include <vector>
    #include <cstddef>
    #include <limits>
    
    template<typename... Ts>
    using TupleVector = std::tuple<std::vector<Ts>...>;
    
    constexpr std::size_t size_t_max = std::numeric_limits<std::size_t>::max();
    
    template<typename T, std::size_t N = size_t_max, typename FuncT>
    void for_all(T b, FuncT F)
    {
      constexpr std::size_t TupSize = std::tuple_size<T>::value;
    
      // Entry point: No 'N' given - default value.
      if constexpr (N == size_t_max) 
      {
        for_all<T,TupSize-1>(b,F); // So call again with tuple size - 1
      }
      else
      {
        // Actually do it. Loop through vector and call lambda, 
        for (auto &i : std::get<TupSize-N-1>(b))
        {
          F(i);
        }
        // If N == 0 escape, otherwise recurse.
        if constexpr (N > 0) 
        {
          for_all<T,N-1>(b,F); // Recursively call with N - 1
        }
      }
    }
    
    struct Dog { int age;  };
    struct Cat { int age;  };
    struct Pig { int age;  };
    struct Cow { int age;  };
    
    int main()
    {
      using farmvec = TupleVector<Dog, Cat, Pig, Cow>;
      farmvec animals;
      std::get<0>(animals).push_back(Dog());
      std::get<0>(animals)[0].age=1;
      std::get<0>(animals).push_back(Dog());
      std::get<0>(animals)[1].age=5;
    
      std::get<1>(animals).push_back(Cat());
      std::get<1>(animals)[0].age=2;
    
      std::get<2>(animals).push_back(Pig());
      std::get<2>(animals)[0].age=3;
    
      std::get<3>(animals).push_back(Cow());
      std::get<3>(animals)[0].age=4;
    
      for_all<farmvec>(animals,[](auto a)
                {
                  std::cout << "Age: " << a.age << std::endl;
                });
      return 0;
    }
    

    如您所见,它使用类型推断与非分层类一起使用,因此我的 Dog、Cat 等无需从基类派生。

    下一步是能够在 lambda 中使用参数,但至少我已经做到了这一点。

    【讨论】:

      【解决方案3】:

      我不确定这是你需要的,对我来说这是一个奇怪的要求:

      template<typename ...Ts>
      using MatrixVariantRows = std::vector<std::variant<std::vector<Ts>...>>;
      

      https://wandbox.org/permlink/Jl9j29TgyaXKZAvh

      【讨论】:

        【解决方案4】:

        根据您的要求,这里还有另一种可能性。权衡是从编译器的角度来看,您可以混合动物或水果(但不能混合动物和水果),但该变体确保在运行时您无法访问不正确的类型。

        struct Dog {};
        struct Cat {};
        struct Pig {};
        struct Cow {};
        struct Apple {};
        struct Pear {};
        struct Orange {};
        struct Lemon {};
        struct Grape {};
        struct Cherry {};
        
        template<typename... Ts>
        using MetaVector = std::vector<std::vector<std::variant<Ts...>>>;
        
        int main(int argc, const char *argv[])
        {
            Dog daisy, sadie, molly;
            Cat sam, coco, tiger;
            Pig frankie, albert, digger;
        
            MetaVector<Dog,Cat,Pig,Cow> animals = {{ daisy, sadie, molly },
                                                   { sam, coco, tiger },
                                                   { frankie, albert, digger }};
            auto dog0 = std::get<Dog>(animals[0][0]);
            auto dog1 = std::get<Dog>(animals[0][1]);
            auto dog2 = std::get<Dog>(animals[0][2]);
        
            auto cat0 = std::get<Cat>(animals[0][0]); // throws exception
        
            MetaVector<Apple,Pear,Orange,Lemon,Grape,Cherry> fruits;
        
            return 0;
        }
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2014-01-09
          • 2021-08-20
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多