【问题标题】:Using argument number in macro expansion?在宏扩展中使用参数号?
【发布时间】:2017-06-29 11:55:24
【问题描述】:

当扩展参数到宏时,有没有办法在宏中包含参数号

这是一个完整的示例,展示了如何使用 trait 为结构分配索引。目前struct_number()总是返回0,是否可以根据宏的参数顺序返回一个常数?

struct Foo {_var: bool}
struct Bar {_var: u8}
struct Baz {_var: i16}

trait NumberStruct {
    fn struct_number() -> usize;
}

macro_rules! number_structs_impl {
    ($($t:ty)*) => ($(
        impl NumberStruct for $t {
            fn struct_number() -> usize {
               // How to return a number based on the argument order?
                return 0;
            }
        }
    )*)
}

number_structs_impl!(Foo Bar Baz);

fn main() {
    // see if the numbers are correct
    macro_rules! print_numbers {
        ($($t:tt)*) => ($(
            print!("{}:{} ", stringify!($t), $t::struct_number());
        )*)
    }

    // should print:
    // Baz:2 Bar:1 Foo:0
    print_numbers!(Baz Bar Foo);
    println!();
}

【问题讨论】:

    标签: macros rust


    【解决方案1】:

    这是可以做到的:

    • 首先计算所有参数。
    • 使用递归宏,因此可以计算 tail* 参数。
    • 可选择将结构列表存储在宏中,这样两个宏调用就不需要重复该列表。

    工作示例:

    struct Foo {_var: bool}
    struct Bar {_var: u8}
    struct Baz {_var: i16}
    
    trait NumberStruct {
        fn struct_number() -> usize;
    }
    
    macro_rules! count_tts {
        () => {0usize};
        ($_head:tt $($tail:tt)*) => {1usize + count_tts!($($tail)*)};
    }
    
    macro_rules! number_structs_impl {
        () => {};
        ($head:tt $($tail:tt)*) => {
            impl NumberStruct for $head {
                fn struct_number() -> usize {
                    return STRUCT_NUM - (1 + count_tts!($($tail)*));
                }
            }
            number_structs_impl!($($tail)*);
        };
    }
    
    // avoid repeating same structs
    macro_rules! apply_structs {
        ($macro_id:ident) => (
            $macro_id! {
                Foo
                Bar
                Baz
            }
        )
    }
    
    const STRUCT_NUM: usize = apply_structs!(count_tts);
    apply_structs!(number_structs_impl);
    
    fn main() {
        // see if the numbers are correct
        macro_rules! print_numbers {
            ($($t:tt)*) => ($(
                print!("{}:{} ", stringify!($t), $t::struct_number());
            )*)
        }
    
        // should print:
        // Baz:2 Bar:1 Foo:0
        print_numbers!(Baz Bar Foo);
        println!();
    }
    

    注意:我发布这个答案是为了展示它的可能,但是它有点混乱,因为它涉及将宏传递给宏和两次调用,如果可以扩展递归宏,这可以更干净地完成,采取last 参数每次递归,see related question

    【讨论】:

      【解决方案2】:

      定义编号实现的一种方法是使用递归宏。 counting arguments 可以创建一个唯一编号,在这种情况下计算尾随参数。

      问题在于,第一个结构的数字最大,最后一个结构为零。

      如果你只需要数字是唯一的,那没关系,但是在这种情况下,我希望每个结构索引都匹配它传递给宏的顺序。

      可以使用递归宏 see this example 反转输入参数。

      使用此宏,可以编写通用宏:

      apply_args_reverse!(macro_name, arg1 arg2 arg3)
      

      扩展为:

      macro_name!(arg3 arg2 arg1)
      

      当然,这本身并不是很有用,但如果参数不是直接写入,而是作为参数传递,它会很有用。

      这可用于创建一个宏,该宏以每个参数的数量进行扩展,如下所示:

      struct Foo {_var: bool}
      struct Bar {_var: u8}
      struct Baz {_var: i16}
      
      trait NumberStruct {
          fn struct_number() -> usize;
      }
      
      macro_rules! count_args_space {
          () => {0_usize};
          ($_head:tt $($tail:tt)*) => {1_usize + count_args_space!($($tail)*)};
      }
      
      macro_rules! number_structs_impl {
          (@single $t:tt $($tail:tt)*) => (
              impl NumberStruct for $t {
                  fn struct_number() -> usize {
                      return count_args_space!($($tail)*);
                  }
              }
          );
      
          () => {};
          ($head:tt $($tail:tt)*) => {
              number_structs_impl!(@single $head $($tail)*);
              number_structs_impl!($($tail)*);
          };
      }
      
      macro_rules! apply_args_reverse {
          ($macro_id:tt [] $($reversed:tt)*) => {
              $macro_id!($($reversed) *);
          };
          ($macro_id:tt [$first:tt $($rest:tt)*] $($reversed:tt)*) => {
              apply_args_reverse!($macro_id [$($rest)*] $first $($reversed)*);
          };
          // Entry point, use brackets to recursively reverse above.
          ($macro_id:tt, $($t:tt)*) => {
              apply_args_reverse!($macro_id [ $($t)* ]);
          };
      }
      
      // Note that both commands below work, and can be swapped to reverse argument order.
      
      // number_structs_impl!(Foo Bar Baz);
      apply_args_reverse!(number_structs_impl, Foo Bar Baz);
      
      fn main() {
          // see if the numbers are correct
          macro_rules! print_numbers {
              ($($t:tt)*) => ($(
                  print!("{}:{} ", stringify!($t), $t::struct_number());
              )*)
          }
      
          print_numbers!(Baz Bar Foo);
          println!();
      }
      

      注意以下陈述:

      number_structs_impl!(Foo Bar Baz);
      

      ...和

      apply_args_reverse!(number_structs_impl, Foo Bar Baz);
      

      ... 是可互换的,被注释的交换会颠倒分配给每个结构的数字顺序。


      注意:保留我的other answer,虽然这更简洁,但它也更脆弱,容易出现难以解决的问题,因为宏扩展嵌套很深(我在让它工作时发现了这一点至少)

      【讨论】:

        猜你喜欢
        • 2016-05-14
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-02-29
        相关资源
        最近更新 更多