在没有枚举和开关的情况下调用许多向量之一的函数
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::vector
s 的内部存储无论如何都会在 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);
我认为 std::variant
和 std::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()
只是为了炫耀。 ;-) 您可能希望为堆栈设置某些但不同的初始大小。
我有一个 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::vector
s 的内部存储无论如何都会在 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);
我认为 std::variant
和 std::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()
只是为了炫耀。 ;-) 您可能希望为堆栈设置某些但不同的初始大小。