是否无法将 运行 次整数作为模板参数传递?
Is it impossible to pass a run-time integer as a template argument?
我有一个基础 class 和一个 class 模板,我希望能够按如下方式实例化它:
class Base
{
};
template<int i>
class Foo
{
};
namespace SomeEnum
{
enum
{
First,
Second,
Third,
...
Last
};
}
void bar()
{
std::unique_ptr<Base> ptr{ nullptr };
int fooType = rand() % SomeEnum::Last;
ptr = std::make_unique<Foo<fooType>> // Causes error because I'm passing a run-time value to something that expects a compile-time value
}
枚举曾经是 class 枚举,但我将其更改为常规枚举,以便我可以使用上面介绍的设计,因为所讨论的枚举非常大。我当时以为我很聪明,但我现在意识到我有点忘记了模板是在编译时处理的。
有没有办法在不完全改变我的设计的情况下避免这种情况?我显然可以做这样的事情:
switch(fooType)
{
case 0:
ptr = std::make_unique<Foo<0>>();
break;
case 1:
ptr = std::make_unique<Foo<1>>();
break;
...
}
但它看起来不是很优雅,而且几乎消除了不只使用 class 枚举的动机,因为我将不得不为枚举中的每个值创建一个 switch case。有没有其他的解决办法,或者我是不是被这个设计逼到了墙角?
有可能让它变得可行。即使用 tuple
存储 std::make_unique
枚举大小对应的函数。
使用 index_sequence
生成 tuple
的辅助方法
template<std::size_t... I>
auto generate(std::index_sequence<I...>)
{
return std::make_tuple(std::make_unique<Foo<I>>...);
}
接下来,为了能够对其进行迭代并找到与所需值匹配的索引,应用递归调用将其增加 1。
template<std::size_t I = 0, typename... Tp, typename std::enable_if<I == sizeof...(Tp)>::type* = nullptr>
void make(const std::tuple<Tp...> &, std::unique_ptr<Base>&, int) // Unused arguments are given no names.
{ return;}
template<std::size_t I = 0, typename... Tp, typename std::enable_if<I < sizeof...(Tp)>::type* = nullptr>
void make(const std::tuple<Tp...>& t, std::unique_ptr<Base>& p, int value)
{
if (value == I) {
p = std::get<I>(t)();
}
make<I + 1, Tp...>(t, p, value);
}
结合以上两种方法,你可以简单地做
auto t = generate(std::make_index_sequence<4>{});
std::unique_ptr<Base> ptr{ nullptr };
int fooType = rand() % SomeEnum::Last;
make(t, ptr, fooType);
一些元编程实用工具:
template<class T>
struct tag_t {using type=T;};
template<class T>
constexpr tag_t<T> tag{};
template<auto I>
using constant_t = std::integral_constant<std::decay_t<decltype(I)>, I>;
template<auto I>
constexpr constant_t<I> constant{};
template<class T, T...ts>
struct sequence{};
我们需要 sequence
,因为 std::integer_sequence
指定过多。
现在我们有了枚举:
enum class SomeEnum {
A,B,C,
ValueCount,
};
一些枚举助手:
template<class E, std::size_t...Is>
constexpr auto EnumsSequence( std::index_sequence<Is...> ) {
return sequence<E, static_cast<E>(Is)...>{};
}
template<class E> // requires E::ValueCount
constexpr std::size_t EnumsSize(tag_t<E> =tag<E>) {
return static_cast<std::size_t>(E::ValueCount);
}
template<class E> // requires E::ValueCount
constexpr auto EnumsSequence(tag_t<E> =tag<E>) {
return EnumsSequence<E>(std::make_index_sequence< EnumsSize(tag<E>) >{} );
}
template<class E>
using make_enums_sequence = decltype( EnumsSequence(tag<E>) );
我们现在可以做 make_enums_sequence<SomeEnum>
并得到 std::integral_sequence<SomeEnum, SomeEnum::A, SomeEnum::B, SomeEnum::C>
。哇,非常基本的编译时反射。它适用于具有 ValueCount
枚举器的任何类型,或者如果您有不同的约定,您可以覆盖特定类型的 EnumsSize(tag_t<E>)
and/or EnumsSequence(tag_t<E>)
。
接下来我们要从 integral_sequence
:
制作一个变体
template<class S>
struct variant_over_helper;
template<class E, E...es>
struct variant_over_helper< sequence<E, es...> > {
using type=std::variant< constant_t<es>... >;
};
template<class E>
using variant_over_t = typename variant_over_helper<make_enums_sequence<E>>::type;
让我们考虑变体 variant_over_t<SomeEnum>
。
它包含它持有的备选方案的索引。备选编号0对应SomeEnum::A
,其值为0。备选编号1对应SomeEnum::B
.
基本上,variant_over_t<SomeEnum>
一个包含与 SomeEnum
的值对齐的整数的结构。除了我们可以 std::visit
这个变体。哇哈哈
接下来我们需要能够将运行时 SomeEnum
转换为 variant_over_t<SomeEnum>
。
template<class E, E... es>
variant_over_t<E> get_variant_enum( sequence<E, es...>, E e ) {
using generator = variant_over_t<E>(*)();
const generator gen[] = {
[]()->variant_over_t<E> {
return constant<es>;
}...
};
return gen[ static_cast<std::size_t>(e) ]();
}
template<class E>
variant_over_t<E> get_variant_enum( E e ) {
return get_variant_enum( EnumsSequence(tag<E>), e );
}
我们完成了。
struct Base{
virtual ~Base() {}
};
enum class SomeEnum
{
First,
Second,
Third,
ValueCount
};
template<SomeEnum i>
struct Foo:Base{};
void bar()
{
std::unique_ptr<Base> ptr;
SomeEnum fooType = static_cast<SomeEnum>( rand() % (int)SomeEnum::ValueCount );
auto vFooType = get_variant_enum(fooType);
ptr = std::visit( [](auto efoo)->std::unique_ptr<Base>{
return std::make_unique<Foo<efoo>>();
}, vFooType );
}
请注意,此 要求 您的枚举是连续的并从 0 开始(对于 gen[]
数组查找)。可以做一些工作来处理不连续的情况,但这会变得很复杂,所以不要不这样做。 (使用 EnumsSequence
构建一个 if-tree 以找到要与 std::in_place_index_t<I>
或 constant<E>
一起使用的索引,and/or 构建一个枚举到索引的映射。)
这段代码运行时间复杂度为O(1),不包括可以初始化一次的数据。
我使用 ValueCount
作为枚举中的元素数。如果你真的需要它被称为Last
,只需添加:
constexpr std::size_t EnumsSize( tag_t<decltype(SomeEnum::Last)> ) {
return static_cast<std::size_t>(SomeEnum::Last);
}
到 tag_t
的命名空间或 SomeEnum
的命名空间,所有代码都通过参数相关查找自动适应。
最精彩的部分?创建 get_variant_enum
的所有元编程都符合:
movzx edx, ah
mov WORD PTR [rsp+14], ax
即复制一些字节过来。
访问调用有点复杂。
一个简单的解决方案是这样的:
namespace detail {
template<size_t I>
std::unique_ptr<Base> makeForIndex() {
return std::make_unique<Foo<I>>();
}
template<size_t... Is>
auto makeFoo(size_t nIdx, std::index_sequence<Is...>) {
using FuncType = std::unique_ptr<Base>(*)();
constexpr FuncType arFuncs[] = {
detail::makeForIndex<Is>...
};
return arFuncs[nIdx]();
}
}
auto makeFoo(size_t nIdx) {
return detail::makeFoo(nIdx, std::make_index_sequence<SomeEnum::Last>());
}
这也不需要模板递归,而且很容易理解。我们创建一个函数指针数组,并使用调用者提供的运行时值对其进行索引。
现在您可以创建您的 Foo<n>
赞
size_t n;
...
auto ptr = makeFoo(n);
我有一个基础 class 和一个 class 模板,我希望能够按如下方式实例化它:
class Base
{
};
template<int i>
class Foo
{
};
namespace SomeEnum
{
enum
{
First,
Second,
Third,
...
Last
};
}
void bar()
{
std::unique_ptr<Base> ptr{ nullptr };
int fooType = rand() % SomeEnum::Last;
ptr = std::make_unique<Foo<fooType>> // Causes error because I'm passing a run-time value to something that expects a compile-time value
}
枚举曾经是 class 枚举,但我将其更改为常规枚举,以便我可以使用上面介绍的设计,因为所讨论的枚举非常大。我当时以为我很聪明,但我现在意识到我有点忘记了模板是在编译时处理的。
有没有办法在不完全改变我的设计的情况下避免这种情况?我显然可以做这样的事情:
switch(fooType)
{
case 0:
ptr = std::make_unique<Foo<0>>();
break;
case 1:
ptr = std::make_unique<Foo<1>>();
break;
...
}
但它看起来不是很优雅,而且几乎消除了不只使用 class 枚举的动机,因为我将不得不为枚举中的每个值创建一个 switch case。有没有其他的解决办法,或者我是不是被这个设计逼到了墙角?
有可能让它变得可行。即使用 tuple
存储 std::make_unique
枚举大小对应的函数。
使用 index_sequence
tuple
的辅助方法
template<std::size_t... I>
auto generate(std::index_sequence<I...>)
{
return std::make_tuple(std::make_unique<Foo<I>>...);
}
接下来,为了能够对其进行迭代并找到与所需值匹配的索引,应用递归调用将其增加 1。
template<std::size_t I = 0, typename... Tp, typename std::enable_if<I == sizeof...(Tp)>::type* = nullptr>
void make(const std::tuple<Tp...> &, std::unique_ptr<Base>&, int) // Unused arguments are given no names.
{ return;}
template<std::size_t I = 0, typename... Tp, typename std::enable_if<I < sizeof...(Tp)>::type* = nullptr>
void make(const std::tuple<Tp...>& t, std::unique_ptr<Base>& p, int value)
{
if (value == I) {
p = std::get<I>(t)();
}
make<I + 1, Tp...>(t, p, value);
}
结合以上两种方法,你可以简单地做
auto t = generate(std::make_index_sequence<4>{});
std::unique_ptr<Base> ptr{ nullptr };
int fooType = rand() % SomeEnum::Last;
make(t, ptr, fooType);
一些元编程实用工具:
template<class T>
struct tag_t {using type=T;};
template<class T>
constexpr tag_t<T> tag{};
template<auto I>
using constant_t = std::integral_constant<std::decay_t<decltype(I)>, I>;
template<auto I>
constexpr constant_t<I> constant{};
template<class T, T...ts>
struct sequence{};
我们需要 sequence
,因为 std::integer_sequence
指定过多。
现在我们有了枚举:
enum class SomeEnum {
A,B,C,
ValueCount,
};
一些枚举助手:
template<class E, std::size_t...Is>
constexpr auto EnumsSequence( std::index_sequence<Is...> ) {
return sequence<E, static_cast<E>(Is)...>{};
}
template<class E> // requires E::ValueCount
constexpr std::size_t EnumsSize(tag_t<E> =tag<E>) {
return static_cast<std::size_t>(E::ValueCount);
}
template<class E> // requires E::ValueCount
constexpr auto EnumsSequence(tag_t<E> =tag<E>) {
return EnumsSequence<E>(std::make_index_sequence< EnumsSize(tag<E>) >{} );
}
template<class E>
using make_enums_sequence = decltype( EnumsSequence(tag<E>) );
我们现在可以做 make_enums_sequence<SomeEnum>
并得到 std::integral_sequence<SomeEnum, SomeEnum::A, SomeEnum::B, SomeEnum::C>
。哇,非常基本的编译时反射。它适用于具有 ValueCount
枚举器的任何类型,或者如果您有不同的约定,您可以覆盖特定类型的 EnumsSize(tag_t<E>)
and/or EnumsSequence(tag_t<E>)
。
接下来我们要从 integral_sequence
:
template<class S>
struct variant_over_helper;
template<class E, E...es>
struct variant_over_helper< sequence<E, es...> > {
using type=std::variant< constant_t<es>... >;
};
template<class E>
using variant_over_t = typename variant_over_helper<make_enums_sequence<E>>::type;
让我们考虑变体 variant_over_t<SomeEnum>
。
它包含它持有的备选方案的索引。备选编号0对应SomeEnum::A
,其值为0。备选编号1对应SomeEnum::B
.
基本上,variant_over_t<SomeEnum>
一个包含与 SomeEnum
的值对齐的整数的结构。除了我们可以 std::visit
这个变体。哇哈哈
接下来我们需要能够将运行时 SomeEnum
转换为 variant_over_t<SomeEnum>
。
template<class E, E... es>
variant_over_t<E> get_variant_enum( sequence<E, es...>, E e ) {
using generator = variant_over_t<E>(*)();
const generator gen[] = {
[]()->variant_over_t<E> {
return constant<es>;
}...
};
return gen[ static_cast<std::size_t>(e) ]();
}
template<class E>
variant_over_t<E> get_variant_enum( E e ) {
return get_variant_enum( EnumsSequence(tag<E>), e );
}
我们完成了。
struct Base{
virtual ~Base() {}
};
enum class SomeEnum
{
First,
Second,
Third,
ValueCount
};
template<SomeEnum i>
struct Foo:Base{};
void bar()
{
std::unique_ptr<Base> ptr;
SomeEnum fooType = static_cast<SomeEnum>( rand() % (int)SomeEnum::ValueCount );
auto vFooType = get_variant_enum(fooType);
ptr = std::visit( [](auto efoo)->std::unique_ptr<Base>{
return std::make_unique<Foo<efoo>>();
}, vFooType );
}
请注意,此 要求 您的枚举是连续的并从 0 开始(对于 gen[]
数组查找)。可以做一些工作来处理不连续的情况,但这会变得很复杂,所以不要不这样做。 (使用 EnumsSequence
构建一个 if-tree 以找到要与 std::in_place_index_t<I>
或 constant<E>
一起使用的索引,and/or 构建一个枚举到索引的映射。)
这段代码运行时间复杂度为O(1),不包括可以初始化一次的数据。
我使用 ValueCount
作为枚举中的元素数。如果你真的需要它被称为Last
,只需添加:
constexpr std::size_t EnumsSize( tag_t<decltype(SomeEnum::Last)> ) {
return static_cast<std::size_t>(SomeEnum::Last);
}
到 tag_t
的命名空间或 SomeEnum
的命名空间,所有代码都通过参数相关查找自动适应。
最精彩的部分?创建 get_variant_enum
的所有元编程都符合:
movzx edx, ah
mov WORD PTR [rsp+14], ax
即复制一些字节过来。
访问调用有点复杂。
一个简单的解决方案是这样的:
namespace detail {
template<size_t I>
std::unique_ptr<Base> makeForIndex() {
return std::make_unique<Foo<I>>();
}
template<size_t... Is>
auto makeFoo(size_t nIdx, std::index_sequence<Is...>) {
using FuncType = std::unique_ptr<Base>(*)();
constexpr FuncType arFuncs[] = {
detail::makeForIndex<Is>...
};
return arFuncs[nIdx]();
}
}
auto makeFoo(size_t nIdx) {
return detail::makeFoo(nIdx, std::make_index_sequence<SomeEnum::Last>());
}
这也不需要模板递归,而且很容易理解。我们创建一个函数指针数组,并使用调用者提供的运行时值对其进行索引。
现在您可以创建您的 Foo<n>
赞
size_t n;
...
auto ptr = makeFoo(n);