If the dimensions are known at compile-time, this can be solved easily with a template that takes dimensions as the non-type argument.
template <std::size_t Dimensions>
void printing(const auto& i){
if constexpr (Dimensions != 0) {
for(const auto& j: i){
// I'm not sure if it is intentional to print 'k' each iteration,
// but this is kept for consistency with the question
cout<<"k: " << Dimensions << endl;
printing<Dimensions - 1u>(j);
}
} else {
cout << setw(3);
cout << j;
cout << '\n';
}
}
The use would be, for a 2d vector:
printing<2>(vec);
Live Example
However, if you always know that const auto& i will be a std::vector type, you can potentially solve this even easier by just not using auto arguments at all, and instead use template matching:
// called only for the vector values
template <typename T>
void printing(const std::vector<T>& i){
for(const auto& j: i){
// possibly compute 'k' to print -- see below
printing(j);
}
}
// Only called for non-vector values
template <typename T>
void printing(const T& v) {
cout << setw(3);
cout << v;
cout << '\n';
}
Live Example
To compute the "dimension" of the vector, you can write a recursive type-trait for that:
#include <type_traits> // std::integral_constant
// Base case: return the count
template <std::size_t Count, typename T>
struct vector_dimension_impl
: std::integral_constant<std::size_t, Count> {};
// Recursive case: add 1 to the count, and check inner type
template <std::size_t Count, typename T, typename Allocator>
struct vector_dimension_impl<Count, std::vector<T,Allocator>>
: vector_dimension_impl<Count + 1u, T> {};
// Dispatcher
template <typename T>
struct vector_dimension : vector_dimension_impl<0u, T> {};
// Convenience access
template <typename T>
inline constexpr auto vector_dimension_v = vector_dimension<T>::value;
// Simple tests:
static_assert(vector_dimension_v<std::vector<int>> == 1u);
static_assert(vector_dimension_v<std::vector<std::vector<int>>> == 2u);
static_assert(vector_dimension_v<std::vector<std::vector<std::vector<int>>>> == 3u);
Live Example
With the above recursive trait, you can get the "dimension" of each templated vector type, without requiring the user to pass in the value at all.
If you still wanted to print k: each time, you can use the above simply with:
cout << "k: " << vector_dimension_v<T> << endl;
This only works if the type is known to be a vector -- but it could be written using concepts to work with anything following the abstract definition of something like a vector as well.
If you want this to work with any range-like type, then you could replace the vector-overload with a requires(std::ranges::range<T>) instead, and change the template-specializations for finding the dimension to also use the same. I won't pollute the answer with all this code since it's largely the same as above -- but I'll link to it in action below:
Live Example