C++:如何在运行时以编程方式定义模板类型?
C++ : How to programmatically define template types in runtime?
我有一个要求,其中有一个枚举,并且为所有可能的枚举组合定义了模板函数,最大长度为 l。
假设枚举是
Enum typenum {A, B, C}
并且所有这些模板函数都在运行时定义并可用(即,编译器在编译时创建这些函数)
Alpha<A>::f()
Alpha<B>::f()
Alpha<C>::f()
Alpha<A,A>::f()
Alpha<A,B>::f()
Alpha<A,C>::f()
Alpha<B,A>::f()
Alpha<B,B>::f()
Alpha<B,C>::f()
Alpha<C,A>::f()
Alpha<C,B>::f()
Alpha<C,C>::f()
and combination of 3 enums, 4 enums...
现在我必须根据输入向量选择正确的函数
void f(vector<enum> eVec){
Alpha::f<eVec[0], eVec[1],... eVec[eVec.size() - 1]>() // <-------
我该怎么做?一种方法是为每个尺寸定义。例如:
if(eVec.size() == 1)
Alpha<eVec[0]>::f()
else if(eVec.size() == 2)
Alpha<eVec[0], eVec[1]>::f()
虽然这不会扩展。有没有优雅的、可扩展的方式来做到这一点。
回答我自己的问题。我意识到这种方法行不通。因为当我们写类似
Alpha<eVec[0]>::f()
编译器抛出错误-“错误:表达式必须有常量值”
所以剩下的唯一选择是
if(eVec.size() == 1){
switch(eVec[0]){
case A:
Alpha<A>::f()
.....
这是因为编译器需要知道所有可能的参数类型,在编译时将使用哪些参数类型调用模板。
And all these template functions are defined and available at runtime (i.e, compiler creates these functions at the compile time)
你确定这是个好主意吗?
因为,如果你想要 select 运行 时间模板值,你必须在编译时实现所有可能的 Alpha<typeNumsValues...>::f()
组合。
如果您不对可变参数列表施加长度限制,那是不可能的,但是当限制相对较低时,计算成本也非常高。
无论如何......假设你有一个枚举如下
enum typeEnum { A, B, C };
和可变参数模板 Alpha
class,具有 typeEnum
模板值和 static
方法 f()
如下
template <typeEnum ...>
struct Alpha
{ static void f () { /* do something */ } };
你的 f()
可以调用可变参数 f_helper()
void f (std::vector<typeEnum> const & eVec)
{ f_helper<>(eVec, 0u); }
实现如下
template <typeEnum ...>
void f_helper (std::vector<typeEnum> const &, ...)
{ }
template <typeEnum ... Tes>
typename std::enable_if<(sizeof...(Tes) < 6u)>::type
f_helper (std::vector<typeEnum> const & eVec, std::size_t index)
{
if ( index < eVec.size() )
switch ( eVec[index++] )
{
case A: f_helper<Tes..., A>(eVec, index); break;
case B: f_helper<Tes..., B>(eVec, index); break;
case C: f_helper<Tes..., C>(eVec, index); break;
}
else
Alpha<Tes...>::f();
}
请注意,我对可变参数列表的长度设置了一个非常低的限制(5,sizeof...(Tes) < 6u
),因为已开发的 Alpha
数量呈指数增长。
还注意到我添加了 f_helper()
的无操作版本;这是必要的,因为长度小于 6 的递归调用 f_helper()
可以使用必须以某种方式管理的 6 个枚举的可变列表来调用它。
下面是一个完整的编译示例
#include <vector>
enum typeEnum { A, B, C };
template <typeEnum ...>
struct Alpha
{ static void f () { } };
template <typeEnum ...>
void f_helper (std::vector<typeEnum> const &, ...)
{ }
template <typeEnum ... Tes>
typename std::enable_if<(sizeof...(Tes) < 6u)>::type
f_helper (std::vector<typeEnum> const & eVec, std::size_t index)
{
if ( index < eVec.size() )
switch ( eVec[index++] )
{
case A: f_helper<Tes..., A>(eVec, index); break;
case B: f_helper<Tes..., B>(eVec, index); break;
case C: f_helper<Tes..., C>(eVec, index); break;
}
else
Alpha<Tes...>::f();
}
void f (std::vector<typeEnum> const & eVec)
{ f_helper<>(eVec, 0u); }
int main ()
{
f({A, B, C, A});
}
如果您需要运行时变量中的特定函数,请改用 map
。模板是不适合这项工作的工具,因为您必须编写很多代码才能将变量转换为常量。
假设您 enum
有一个默认值 None
并且您有最多 5 个参数,您可以定义一个这样的映射:
enum MyEnum { None = 0, A, B, C, D... };
using MyKey = std::tuple<MyEnum, MyEnum, MyEnum, MyEnum, MyEnum>;
using MyFunction = std::function<void()>;
然后你在某个地方有一个函数映射(一个单例)
std::map<MyKey, MyFunction> myMap;
实用函数可能有助于从可变数量的参数创建密钥:
MyKey MakeKey(MyEnum e1, MyEnum e2 = None, MyEnum e3 = None, MyEnum e4 = None, MyEnum e5 = None)
{
return std::make_tuple(e1, e2, e3, e4, e5);
}
myMap.emplace(MakeKey(A, B), [](){ /* some code */ });
MyEnum AtOrDefault(const vector<enum> &eVec, int index)
{
return index < eVec.size() ? eVec[index] : None;
}
然后假设您想从向量中调用适当的函数,您可以这样做:
void f(const vector<enum> &eVec)
{
if (eVec.size() > 5) throw some_exception;
MyKey key = std::make_typle(
AtOrDefault(eVec, 0),
AtOrDefault(eVec, 1),
AtOrDefault(eVec, 2),
AtOrDefault(eVec, 3),
AtOrDefault(eVec, 4));
auto &fn = myMap[key];
fn();
}
您还可以使用计算值的想法,假设您知道枚举中元素的最大数量。然后您可以创建一个 CombinedEnumType:
enum CombinedEnumType : uint32_t { };
并定义了一个函数
CombinedEnumType MakeCombinedEnumType(MyEnum e1, … MyEnum e5 = None)
{
const int MyEnumEcount = 5;
MyEnum a[] = { e1, e2, e3, e4, e5 };
uint32_t result = 0;
for (auto & item : a) { result *= MyEnumEcount; result += item; }
return static_cast<CombinedEnumType>(result);
}
此代码仅供参考。在实际生产代码中,您必须使用常量、正确的变量名、验证给定组合是否存在函数……
我有一个要求,其中有一个枚举,并且为所有可能的枚举组合定义了模板函数,最大长度为 l。
假设枚举是
Enum typenum {A, B, C}
并且所有这些模板函数都在运行时定义并可用(即,编译器在编译时创建这些函数)
Alpha<A>::f()
Alpha<B>::f()
Alpha<C>::f()
Alpha<A,A>::f()
Alpha<A,B>::f()
Alpha<A,C>::f()
Alpha<B,A>::f()
Alpha<B,B>::f()
Alpha<B,C>::f()
Alpha<C,A>::f()
Alpha<C,B>::f()
Alpha<C,C>::f()
and combination of 3 enums, 4 enums...
现在我必须根据输入向量选择正确的函数
void f(vector<enum> eVec){
Alpha::f<eVec[0], eVec[1],... eVec[eVec.size() - 1]>() // <-------
我该怎么做?一种方法是为每个尺寸定义。例如:
if(eVec.size() == 1)
Alpha<eVec[0]>::f()
else if(eVec.size() == 2)
Alpha<eVec[0], eVec[1]>::f()
虽然这不会扩展。有没有优雅的、可扩展的方式来做到这一点。
回答我自己的问题。我意识到这种方法行不通。因为当我们写类似
Alpha<eVec[0]>::f()
编译器抛出错误-“错误:表达式必须有常量值”
所以剩下的唯一选择是
if(eVec.size() == 1){
switch(eVec[0]){
case A:
Alpha<A>::f()
.....
这是因为编译器需要知道所有可能的参数类型,在编译时将使用哪些参数类型调用模板。
And all these template functions are defined and available at runtime (i.e, compiler creates these functions at the compile time)
你确定这是个好主意吗?
因为,如果你想要 select 运行 时间模板值,你必须在编译时实现所有可能的 Alpha<typeNumsValues...>::f()
组合。
如果您不对可变参数列表施加长度限制,那是不可能的,但是当限制相对较低时,计算成本也非常高。
无论如何......假设你有一个枚举如下
enum typeEnum { A, B, C };
和可变参数模板 Alpha
class,具有 typeEnum
模板值和 static
方法 f()
如下
template <typeEnum ...>
struct Alpha
{ static void f () { /* do something */ } };
你的 f()
可以调用可变参数 f_helper()
void f (std::vector<typeEnum> const & eVec)
{ f_helper<>(eVec, 0u); }
实现如下
template <typeEnum ...>
void f_helper (std::vector<typeEnum> const &, ...)
{ }
template <typeEnum ... Tes>
typename std::enable_if<(sizeof...(Tes) < 6u)>::type
f_helper (std::vector<typeEnum> const & eVec, std::size_t index)
{
if ( index < eVec.size() )
switch ( eVec[index++] )
{
case A: f_helper<Tes..., A>(eVec, index); break;
case B: f_helper<Tes..., B>(eVec, index); break;
case C: f_helper<Tes..., C>(eVec, index); break;
}
else
Alpha<Tes...>::f();
}
请注意,我对可变参数列表的长度设置了一个非常低的限制(5,sizeof...(Tes) < 6u
),因为已开发的 Alpha
数量呈指数增长。
还注意到我添加了 f_helper()
的无操作版本;这是必要的,因为长度小于 6 的递归调用 f_helper()
可以使用必须以某种方式管理的 6 个枚举的可变列表来调用它。
下面是一个完整的编译示例
#include <vector>
enum typeEnum { A, B, C };
template <typeEnum ...>
struct Alpha
{ static void f () { } };
template <typeEnum ...>
void f_helper (std::vector<typeEnum> const &, ...)
{ }
template <typeEnum ... Tes>
typename std::enable_if<(sizeof...(Tes) < 6u)>::type
f_helper (std::vector<typeEnum> const & eVec, std::size_t index)
{
if ( index < eVec.size() )
switch ( eVec[index++] )
{
case A: f_helper<Tes..., A>(eVec, index); break;
case B: f_helper<Tes..., B>(eVec, index); break;
case C: f_helper<Tes..., C>(eVec, index); break;
}
else
Alpha<Tes...>::f();
}
void f (std::vector<typeEnum> const & eVec)
{ f_helper<>(eVec, 0u); }
int main ()
{
f({A, B, C, A});
}
如果您需要运行时变量中的特定函数,请改用 map
。模板是不适合这项工作的工具,因为您必须编写很多代码才能将变量转换为常量。
假设您 enum
有一个默认值 None
并且您有最多 5 个参数,您可以定义一个这样的映射:
enum MyEnum { None = 0, A, B, C, D... };
using MyKey = std::tuple<MyEnum, MyEnum, MyEnum, MyEnum, MyEnum>;
using MyFunction = std::function<void()>;
然后你在某个地方有一个函数映射(一个单例)
std::map<MyKey, MyFunction> myMap;
实用函数可能有助于从可变数量的参数创建密钥:
MyKey MakeKey(MyEnum e1, MyEnum e2 = None, MyEnum e3 = None, MyEnum e4 = None, MyEnum e5 = None)
{
return std::make_tuple(e1, e2, e3, e4, e5);
}
myMap.emplace(MakeKey(A, B), [](){ /* some code */ });
MyEnum AtOrDefault(const vector<enum> &eVec, int index)
{
return index < eVec.size() ? eVec[index] : None;
}
然后假设您想从向量中调用适当的函数,您可以这样做:
void f(const vector<enum> &eVec)
{
if (eVec.size() > 5) throw some_exception;
MyKey key = std::make_typle(
AtOrDefault(eVec, 0),
AtOrDefault(eVec, 1),
AtOrDefault(eVec, 2),
AtOrDefault(eVec, 3),
AtOrDefault(eVec, 4));
auto &fn = myMap[key];
fn();
}
您还可以使用计算值的想法,假设您知道枚举中元素的最大数量。然后您可以创建一个 CombinedEnumType:
enum CombinedEnumType : uint32_t { };
并定义了一个函数
CombinedEnumType MakeCombinedEnumType(MyEnum e1, … MyEnum e5 = None)
{
const int MyEnumEcount = 5;
MyEnum a[] = { e1, e2, e3, e4, e5 };
uint32_t result = 0;
for (auto & item : a) { result *= MyEnumEcount; result += item; }
return static_cast<CombinedEnumType>(result);
}
此代码仅供参考。在实际生产代码中,您必须使用常量、正确的变量名、验证给定组合是否存在函数……