Bouraoui Hamadou
Bouraoui Hamadou

Reputation: 65

Iterate throught n-dimensional vector c++

I wanted to write my own code to iterate over an n dimensional vector (where the dimension is known). Here is the code:

void printing(const auto& i, const int dimension){
    int k= dimension;
    for(const auto& j: i){
        if(k>1){
            cout<<"k: "<<k<<endl;
            printing(j, --k);
        }
        else{
            //start printing
            cout<<setw(3);
            cout<<j; //not quite sure about this line
        }
        cout<<'\n';
    }
}

I get an error:

main.cpp:21:5: error: ‘begin’ was not declared in this scope
 for(const auto& j: i){
 ^~~

Could someone help me to correct it or give me a better way to print the vector? Thanks in advance for your time.

Upvotes: 2

Views: 308

Answers (2)

Claudiu HBann
Claudiu HBann

Reputation: 54

I have made a function that can print any n-dimensional iterable container:

template<typename Object, typename Iterable>
void Print(
    const Iterable& iterable,
    const string& separatorDimensions = "\n",
    const function<void(const Object&)>& funcPrintElem = [] (const Object& obj) {
        static_assert(
            is_arithmetic_v<Object> || is_same_v<remove_const_t<remove_pointer_t<Object>>, char>,
            R"(The object from the innermost range is not a built-in/c-string type, please provide a valid print element function.)"
            );
        cout << obj << ' ';
    }
) {
    if constexpr (ranges::range<Iterable>) {
        ranges::for_each(iterable, [&] (const auto& it) { Print(it, separatorDimensions, funcPrintElem); });
        cout << separatorDimensions;
    } else {
        funcPrintElem(iterable);
    }
}

The function has a default std::function that can print any built-in type like int, unsigned char, long long etc... and the c-string like char* or const char*, if you have another object like a pair or tuple or an object of your class you can pass a function that prints your object.

You can use the function like this: (you must explicitly tell the function your inner most object like below)

int main() {
    cout << "v: " << endl;
    vector<uint16_t> v { 1, 2, 3 };
    Print<uint16_t>(v);

    cout << endl << "ll: " << endl;
    list<list<const char*>> ll { { "a", "b" }, { "c", "d" } };
    Print<const char*>(ll);

    struct smth {
        int a;
        char b;
    };

    cout << endl << "smths: " << endl;
    vector<smth> smths { { 14, '0' }, { 18, '1' } };
    Print<smth>(smths, "\n", [] (const smth& obj) { cout << "a = " << obj.a << ", b = " << obj.b << endl; });

    return 0;
}

The function can be found here, maybe I will update in the future to support more things.

Edit: You need to have at least c++20 for this function to work

Upvotes: 0

Bitwize
Bitwize

Reputation: 11220

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

Upvotes: 2

Related Questions