Compile time checks for const template parameter

I have an issues that looks like it should have a trivial solution, but for the life of me, I cannot figure it out.

So I have defined the following template enum

pub enum ModelParameters<const D: usize> {
  Width,
  Height,
  Slope,
  Left(u8),
  Right(u8),
}

The idea here is to use this enum as indices to a model I'm trying to fit. I want to be able to change easily the number of parameters. Here D is the total number of parameters. The left and right parameters are balanced, so if there is N of them, D = 2*N + 3.

My first idea is to have N as the type parameter, but I need D as a template parameter for nalgebra so I ended up using D as template parameter and calculating N when I need, N = (D - 3)/2.

What I want now is to make sure that N makes sense, so I need D to satisfy the following

D >= 3 and D % 2 == 1 and D < 515.

I would like this check to happen once in compile time, but I cannot make this happen.

I have defined the following function

impl<const D: usize> ModelParameters<D> {
  pub const COUNT: usize = D;

  pub const fn number_of_sides() -> usize {
    assert!(D >= 3 && D % 2 == 1 && D < 515);
    let n = (D - 3) / 2;
    return n;
  }

Then I thought that if I can force the compiler to run the function, then it will throw an error. I realised that I cannot do this with assert. Then I tried my luck with const_assert from the static-assertions crate. This give me the following error:

    |
 97 | impl<const D: usize> SkutVolModelParameters<D> {
    |            - const parameter from outer item
...
101 |     const_assert!(D >= 3 && D % 2 == 1 && D < 515);
    |                                           ^ use of generic parameter from outer item
    |
    = note: a `const` is a separate item from the item that contains it

What does this even mean? What is the "outer item"? Is there a way to have a compile time check in this case?

The "outer item" is the impl block, whereas the fn is the inner item here. So the solution is to move the check outside the function. But you have to actually use the result of the check:

pub enum ModelParameters<const D: usize> {
  Width,
  Height,
  Slope,
  Left(u8),
  Right(u8),
}

impl<const D: usize> ModelParameters<D> {
  pub const COUNT: usize = D;
  const VALID: () = assert!(D >= 3 && D %2 ==1 && D < 515);

  pub const fn number_of_sides() -> usize {
    let _ = Self::VALID;
    let n = (D - 3) / 2;
    return n;
  }
}

fn main() {
    dbg!(ModelParameters::<3>::number_of_sides());
    dbg!(ModelParameters::<2>::number_of_sides());
}
1 Like

I would probably write this with inline const (assuming a high enough MSRV):

impl<const D: usize> ModelParameters<D> {
  pub const COUNT: usize = D;

  pub const fn number_of_sides() -> usize {
    const {
        assert!(D >= 3 && D % 2 == 1 && D < 515);
    }
    let n = (D - 3) / 2;
    return n;
  }
}
4 Likes

You can do this with an assert inside a const { /* .. */ } block on recent compiler versions (1.79.0 and later). Looks like static-assertions probably supports 1.37 (and older versions of the crate go even further back); that is, there’s good reason for the static-assertions crate to exist, but it shouldn’t be as necessary anymore.

1 Like

Thank you! Both your solution robofinch's solution work. It straightforward enough for me to feel embarrassed for not figuring it out :smiley:

Thanks! As I said above, both your solutions work and it feels like I should have thought of that :smiley: