Going back to the “type deduction” based idea: instead of
fn foo<const N: usize>(_n: N) { … }
which, as @rkruppe noted, creates a sort of semantic confusion:
what about having an explicit ‘lifting’ type? So you would instead write
fn foo<const N: usize>(_n: Const<usize, N>) { … }
where Const is a zero-sized type defined as
struct Const<T, const Val: T>;
The only compiler magic would be an automatic coercion (applied in the same places as existing coercions) from a constant expression of type T to the type Const<T, Val>, substituting in the actual value of the expression. So when calling foo, you wouldn’t have to explicitly construct the Const object, you would just pass the number. As another example, this (useless) code would compile:
const X: usize = 42;
let xconst: Const<usize, 42> = X;
let xconst2: Const<_, _> = X; // works with type inference
To be honest, this approach feels kind of ‘C++-ish’ to me (even though C++ doesn’t actually have an equivalent!), and I don’t know whether it’s ideal. But it would be considerably less magical than const function arguments. In particular, it would preserve the one-to-one mapping between function arguments and fields of the Fn trait tuple.