在没有枚举和开关的情况下调用许多向量之一的函数

Call function on one of many vectors without enum and switch

我有一个 class,其中包含多个不相关的 class 向量。

class Class0 {};
class Class1 {};
class Class2 {};

enum class RemoveFromVector : uint8_t { CLASS0 = 0, CLASS1 = 1, CLASS2 = 2 };

class C
{
public:
   std::vector<Class0> c0s;
   std::vector<Class1> c1s;
   std::vector<Class2> c2s;

   void RemoveFromVector(const RemoveFromVector RFV, const int Index)
   {
      // I'd like to replace this switch with something that's compile-time
      switch ((uint8_t)RFV)
      {
      case 0: c0s.erase(c0s.begin() + Index); break;
      case 1: c1s.erase(c1s.begin() + Index); break;
      case 2: c2s.erase(c2s.begin() + Index); break;
      default: break;
      }
   }
};

int main()
{
   C c;
   c.c0s.push_back(Class0());
   c.c1s.push_back(Class1());
   c.c1s.push_back(Class1());
   c.c1s.push_back(Class1());
   c.c2s.push_back(Class2());

   // this should remove the last element from C.c1s
   c.RemoveFromVector(RemoveFromVector::CLASS1, 2);
}

我将如何编写一个函数,在运行时根据 enum(或 int)从一个向量中删除一个元素,而无需编写开关每个向量都有一个案例?

换句话说,我正在寻找一种方法来推断要在编译时静态调用 erase() 的向量,然后在运行时调用它。正确的术语可能是“静态调度”,但我不完全确定。

您应该使代码尽可能简单。根据当前显示的代码,对于以后在代码库上工作的每个开发人员来说,它都是简单易读的。

其次,通过 std::vectors 的内部存储无论如何都会在 run-time 完成此任务。因为,大多数操作发生在 std::vector 是 运行 时间开销,因为它们分配内存并在 运行 时间管理它。因此,您不能为 std::vector::erase 做任何 compile-time 工作。


话虽这么说,如果你坚持避免 switch 语句,并带来模板复杂性,下面是一个,它仍然会有使用 if constexpr 的模板类型映射,并且矢量擦除发生在 run-time.

#include <type_traits> // std::is_same_v

class C
{
   template<typename ClassType>
   auto& getVectorOf() /* noexcept */
   {
      if constexpr (std::is_same_v<ClassType, Class0>) return c0s;
      else if constexpr (std::is_same_v<ClassType, Class1>) return c1s;
      else if constexpr (std::is_same_v<ClassType, Class2>) return c2s;
   }
public:
   std::vector<Class0> c0s; // recommended to be private!
   std::vector<Class1> c1s;
   std::vector<Class2> c2s;

   template<typename ClassType>
   void RemoveFromVector(const std::size_t index) /* noexcept */
   {
      // some index check!
      auto& vec = getVectorOf<ClassType>();
      vec.erase(vec.begin() + index);
   }
};

像这样调用函数

C c;
// fill the vectors
c.RemoveFromVector<Class0>(0);
c.RemoveFromVector<Class1>(2);
c.RemoveFromVector<Class2>(0);

(See a Demo Online)

我认为 std::variantstd::visit 是您可以走的路。您还可以使用新的辅助函数 make_variant_array 使代码更短:


#include <cstdint>
#include <vector>
#include <variant>
#include <array>
#include <functional>

class Class0 {};
class Class1 {};
class Class2 {};

enum class RemoveFromVector
    : uint8_t
{
    Class0 = 0, Class1 = 1, Class2 = 2
};

template <class... Args>
auto make_variant_array(Args&... args)
{
    using var_t = std::variant<std::reference_wrapper<Args>...>;
    return std::array<var_t, sizeof...(Args)>{std::ref(args)...};
}

class C
{
public:
    std::vector<Class0> c0s;
    std::vector<Class1> c1s;
    std::vector<Class2> c2s;

    void RemoveFromVector(const RemoveFromVector RFV, const int Index)
    {
        static auto lookup = make_variant_array(c0s, c1s, c2s);
        std::visit([Index](auto& vec) { vec.get().erase(vec.get().begin() + Index); }, lookup[(uint8_t)RFV]);
    }
};

int main()
{
    C c;
    c.c0s.push_back(Class0());
    c.c1s.push_back(Class1());
    c.c1s.push_back(Class1());
    c.c1s.push_back(Class1());
    c.c2s.push_back(Class2());

    // this should remove the last element from C.c1s
    c.RemoveFromVector(RemoveFromVector::Class1, 2);
}

这里有一个更通用的解决方案:

    template <typename... Types>
    class MultiStack
    {        
    public:

        template <typename T>
        /*const*/ T& Get() /*const*/
        {
            return GetStack<T>().back();
        }        

        template <typename T>
        void Push( const T& t )
        {
            GetStack<T>().push_back( t );
        }

        template <typename T>
        void Pop()
        {
            GetStack<T>().pop_back();
        }
        
        template <size_t... Sizes>
        void Reserve()
        {
            auto reserve = [&]( auto&... stacks ) { ( stacks.reserve( Sizes ), ... ); };
            std::apply( reserve, Stacks );
        }

    private:

        template <typename T>
        std::vector<T>& GetStack()
        { 
            return std::get<std::vector<T>>( Stacks ); 
        }

        std::tuple<std::vector<Types>...> Stacks;

    };

用法看起来很简单:

using MyStack = MultiStack<Class0,Class1,Class2>;
MyStack stack;

stack.Push( ClassX() );  // automatically pushes any compatible object on the appropriate stack

stack.Pop<Class1>();     // pops/"erases" last object of the Class1 stack (vector)

如果您需要一次 Pop 多个对象,或者 'call other functions' 在矢量上,您可以扩展它。您 could/should 也可以根据需要制作 Get return const T&。 (Get() const 需要 GetStack() const

我看中了Reserve()只是为了炫耀。 ;-) 您可能希望为堆栈设置某些但不同的初始大小。