使用宏编写 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
    }
}

假设 NM 在 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);

(Playground)

为了获得不那么乏味的宏,有几个相关问题和有用的答案 (, ) 我认为我可以适应,但在这两种情况下都可以在宏内重复函数调用,这似乎简化了事情。例如,使用第一个链接示例中的方法,我开始:

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)
}

(Playground)

无法编译,因为编译器不希望在枚举变量位置有宏(我相信)。

(明确地说,我不一定想将上面的内容用于任何严肃的事情,我只是 运行 在试验和好奇的时候进入它。)

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。所有这些都完成,直到所有 nm 都用完。

中间结果被收集到宏的第一个参数中,最后传递给您的 impl_foo_enum