Yes, that’s why I didn’t cite the existence of these constants in general as a problem. However, this solution moves any calculations involving them to run-time, which means they aren’t available e.g. when constructing vtables.
Sorry, to make explicit what I was thinking of:
// base case, "always applicable" if we extend that notion to const generics
impl<T, N: const usize> NAryTuple<T, N> { ... }
// a bunch of specializations for specific values of N
impl<T> NAryTuple<T, 1> { ... }
impl<T> NAryTuple<T, 2> { ... }
impl<T> NAryTuple<T, 3> { ... }
// ...
Now, NAryTuple::<T::CONST, T>::print refers to the specialization for the value of N = T::CONST if it exists, and the generic base impl otherwise. To implement this via vtable-passing, we need a unified vtable that gives us the print implementation for every n (since the caller doesn’t necessarily know which values of N the callee wants to use, they can’t create and pass a vtable precisely for this value). Normally – I believe even with type specialization involved – a unified vtable contains a relatively efficient flat array of precisely the methods that exist and anyone using the vtable knows at which offset each method is. This doesn’t work in this case because any crate can add a new specialization for a new constant value for a specific type, e.g.
struct Foo;
impl NAryTuple<Foo, 47> { ... }
While I suppose you can put a more involved data structure into the vtable (e.g. a search tree with concrete specializations in the leaves) this makes virtual calls much more involved and expensive.