使用宏编写 const 通用枚举组合
Writing const generic enum combinations using macro
考虑一个带有两个常量泛型参数的玩具结构:
pub struct Foo<const N: usize, const M: usize>([usize; N], [usize; M]);
impl<const N: usize, const M: usize> Foo<N, M> {
pub fn bar(&self) -> usize {
N * M
}
}
假设 N
和 M
在 1 和 5 之间的所有组合都是允许的,这样我们就可以编写以下枚举:
pub enum FooEnum {
Foo_1_1(Foo<1, 1>),
Foo_1_2(Foo<1, 2>),
Foo_2_1(Foo<2, 1>),
Foo_2_2(Foo<2, 2>),
// ... and so on.
}
impl FooEnum {
pub fn bar(&self) -> usize {
match self {
Self::Foo_1_1(x) => x.bar(),
Self::Foo_1_2(x) => x.bar(),
Self::Foo_2_1(x) => x.bar(),
Self::Foo_2_2(x) => x.bar(),
// ... and so on.
}
}
}
我的问题是:我们可以编写一个声明性宏来生成它,而无需手动写出所有组合吗?也就是说,类似于 impl_foo_enum!(1, 2, 3, 4, 5)
,而不是 impl_foo_enum!(1;1, 1;2, 1;3, [...and so on])
。
我可以使用 paste
板条箱编写后一个宏:
macro_rules! impl_foo_enum {
($($n:literal;$m:literal),+) => {
paste::paste! {
pub enum FooEnum2 {
$(
[<Foo _ $n _ $m>](Foo<$n, $m>)
),+
}
impl FooEnum2 {
pub fn bar(&self) -> usize {
match self {
$(Self::[<Foo _ $n _ $m>](x) => x.bar()),+
}
}
}
}
}
}
impl_foo_enum!(1;1, 1;2, 2;1, 2;2);
为了获得不那么乏味的宏,有几个相关问题和有用的答案 (, ) 我认为我可以适应,但在这两种情况下都可以在宏内重复函数调用,这似乎简化了事情。例如,使用第一个链接示例中的方法,我开始:
macro_rules! for_all_pairs {
($mac:ident: $($x:literal)*) => {
for_all_pairs!(@inner $mac: $($x)*; $($x)*);
};
(@inner $mac:ident: ; $($x:literal)*) => {};
(@inner $mac:ident: $head:literal $($tail:literal)*; $($x:literal)*) => {
$(
$mac!($head $x);
)*
for_all_pairs!(@inner $mac: $($tail)*; $($x)*);
};
}
macro_rules! impl_foo_enum {
($n:literal $m:literal) => {
paste::paste! { [<Foo _ $n _ $m>](Foo<$n, $m>) }
}
}
pub enum FooEnum3 {
for_all_pairs!(impl_foo_enum: 1 2)
}
无法编译,因为编译器不希望在枚举变量位置有宏(我相信)。
(明确地说,我不一定想将上面的内容用于任何严肃的事情,我只是 运行 在试验和好奇的时候进入它。)
Here你去:
#![allow(non_camel_case_types)]
pub struct Foo<const N: usize, const M: usize>([usize; N], [usize; M]);
impl<const N: usize, const M: usize> Foo<N, M> {
pub fn bar(&self) -> usize {
N * M
}
}
macro_rules! impl_foo_2{
($($n:literal)*) => {
impl_foo_2!([] @orig($($n)*) ($($n)*) ($($n)*));
};
(
[$(($n:literal $m:literal))*]
@orig($($n_orig:literal)*)
($($n_unused:literal)*) ()
) => {
paste::paste! {
pub enum FooEnum2 {
$([<Foo _ $n _ $m>](Foo<$n, $m>)),+
}
impl FooEnum2 {
pub fn bar(&self) -> usize {
match self {
$(Self::[<Foo _ $n _ $m>](x) => x.bar()),+
}
}
}
}
};
(
[$($t:tt)*]
@orig($($n_orig:literal)*)
() ($m0:literal $($m:literal)*)
) => {
impl_foo_2!(
[$($t)*]
@orig($($n_orig)*)
($($n_orig)*) ($($m)*)
);
};
(
[$($t:tt)*]
@orig($($n_orig:literal)*)
($n0:literal $($n:literal)*) ($m0:literal $($m:literal)*)
) => {
impl_foo_2!(
[$($t)* ($n0 $m0)]
@orig($($n_orig)*)
($($n)*) ($m0 $($m)*)
);
}
}
impl_foo_2!(1 2 3 4 5);
impl_foo_2
在内部生成您的号码列表的两个相同副本。然后它继续并一次处理一个 m
,将其与每个 n
组合(它通过重复砍掉第一个 n
来实现)。如果 n
-list 已用完,它会重置 n
-list,并砍掉第一个 m
。所有这些都完成,直到所有 n
和 m
都用完。
中间结果被收集到宏的第一个参数中,最后传递给您的 impl_foo_enum
。
考虑一个带有两个常量泛型参数的玩具结构:
pub struct Foo<const N: usize, const M: usize>([usize; N], [usize; M]);
impl<const N: usize, const M: usize> Foo<N, M> {
pub fn bar(&self) -> usize {
N * M
}
}
假设 N
和 M
在 1 和 5 之间的所有组合都是允许的,这样我们就可以编写以下枚举:
pub enum FooEnum {
Foo_1_1(Foo<1, 1>),
Foo_1_2(Foo<1, 2>),
Foo_2_1(Foo<2, 1>),
Foo_2_2(Foo<2, 2>),
// ... and so on.
}
impl FooEnum {
pub fn bar(&self) -> usize {
match self {
Self::Foo_1_1(x) => x.bar(),
Self::Foo_1_2(x) => x.bar(),
Self::Foo_2_1(x) => x.bar(),
Self::Foo_2_2(x) => x.bar(),
// ... and so on.
}
}
}
我的问题是:我们可以编写一个声明性宏来生成它,而无需手动写出所有组合吗?也就是说,类似于 impl_foo_enum!(1, 2, 3, 4, 5)
,而不是 impl_foo_enum!(1;1, 1;2, 1;3, [...and so on])
。
我可以使用 paste
板条箱编写后一个宏:
macro_rules! impl_foo_enum {
($($n:literal;$m:literal),+) => {
paste::paste! {
pub enum FooEnum2 {
$(
[<Foo _ $n _ $m>](Foo<$n, $m>)
),+
}
impl FooEnum2 {
pub fn bar(&self) -> usize {
match self {
$(Self::[<Foo _ $n _ $m>](x) => x.bar()),+
}
}
}
}
}
}
impl_foo_enum!(1;1, 1;2, 2;1, 2;2);
为了获得不那么乏味的宏,有几个相关问题和有用的答案 (
macro_rules! for_all_pairs {
($mac:ident: $($x:literal)*) => {
for_all_pairs!(@inner $mac: $($x)*; $($x)*);
};
(@inner $mac:ident: ; $($x:literal)*) => {};
(@inner $mac:ident: $head:literal $($tail:literal)*; $($x:literal)*) => {
$(
$mac!($head $x);
)*
for_all_pairs!(@inner $mac: $($tail)*; $($x)*);
};
}
macro_rules! impl_foo_enum {
($n:literal $m:literal) => {
paste::paste! { [<Foo _ $n _ $m>](Foo<$n, $m>) }
}
}
pub enum FooEnum3 {
for_all_pairs!(impl_foo_enum: 1 2)
}
无法编译,因为编译器不希望在枚举变量位置有宏(我相信)。
(明确地说,我不一定想将上面的内容用于任何严肃的事情,我只是 运行 在试验和好奇的时候进入它。)
Here你去:
#![allow(non_camel_case_types)]
pub struct Foo<const N: usize, const M: usize>([usize; N], [usize; M]);
impl<const N: usize, const M: usize> Foo<N, M> {
pub fn bar(&self) -> usize {
N * M
}
}
macro_rules! impl_foo_2{
($($n:literal)*) => {
impl_foo_2!([] @orig($($n)*) ($($n)*) ($($n)*));
};
(
[$(($n:literal $m:literal))*]
@orig($($n_orig:literal)*)
($($n_unused:literal)*) ()
) => {
paste::paste! {
pub enum FooEnum2 {
$([<Foo _ $n _ $m>](Foo<$n, $m>)),+
}
impl FooEnum2 {
pub fn bar(&self) -> usize {
match self {
$(Self::[<Foo _ $n _ $m>](x) => x.bar()),+
}
}
}
}
};
(
[$($t:tt)*]
@orig($($n_orig:literal)*)
() ($m0:literal $($m:literal)*)
) => {
impl_foo_2!(
[$($t)*]
@orig($($n_orig)*)
($($n_orig)*) ($($m)*)
);
};
(
[$($t:tt)*]
@orig($($n_orig:literal)*)
($n0:literal $($n:literal)*) ($m0:literal $($m:literal)*)
) => {
impl_foo_2!(
[$($t)* ($n0 $m0)]
@orig($($n_orig)*)
($($n)*) ($m0 $($m)*)
);
}
}
impl_foo_2!(1 2 3 4 5);
impl_foo_2
在内部生成您的号码列表的两个相同副本。然后它继续并一次处理一个 m
,将其与每个 n
组合(它通过重复砍掉第一个 n
来实现)。如果 n
-list 已用完,它会重置 n
-list,并砍掉第一个 m
。所有这些都完成,直到所有 n
和 m
都用完。
中间结果被收集到宏的第一个参数中,最后传递给您的 impl_foo_enum
。