Generate Arbitrarily Nested Vectors in C++

允我心安 提交于 2021-02-07 10:28:51

问题


I am trying to write a function in order to generate arbitrarily nested vectors and initialize with the given specific value in C++. For example, auto test_vector = n_dim_vector_generator<2, long double>(static_cast<long double>(1), 1); is expected to create a "test_vector" object which type is std::vector<std::vector<long double>>. The content of this test_vector should as same as the following code.

    std::vector<long double> vector1;
    vector1.push_back(1);
    std::vector<std::vector<long double>> test_vector;
    test_vector.push_back(vector1);

The more complex usage of the n_dim_vector_generator function:

auto test_vector2 = n_dim_vector_generator<15, long double>(static_cast<long double>(2), 3);

In this case, the parameter static_cast<long double>(2) is as the data in vectors and the number 3 is as the push times. So, the content of this test_vector2 should as same as the following code.

    std::vector<long double> vector1;
    vector1.push_back(static_cast<long double>(2));
    vector1.push_back(static_cast<long double>(2));
    vector1.push_back(static_cast<long double>(2));
    std::vector<std::vector<long double>> vector2;
    vector2.push_back(vector1);
    vector2.push_back(vector1);
    vector2.push_back(vector1);
    std::vector<std::vector<std::vector<long double>>> vector3;
    vector3.push_back(vector2);
    vector3.push_back(vector2);
    vector3.push_back(vector2);
    //...Totally repeat 15 times in order to create test_vector2
    std::vector<...std::vector<long double>> test_vector2;
    test_vector2.push_back(vector14);
    test_vector2.push_back(vector14);
    test_vector2.push_back(vector14);

The detail to implement n_dim_vector_generator function is as follows.

#include <iostream>
#include <vector>

template <typename T, std::size_t N>
struct n_dim_vector_type;

template <typename T>
struct n_dim_vector_type<T, 0> {
    using type = T;
};

template <typename T, std::size_t N>
struct n_dim_vector_type {
    using type = std::vector<typename n_dim_vector_type<T, N - 1>::type>;
};


template<std::size_t N, typename T>
typename n_dim_vector_type<T,N>::type n_dim_vector_generator(T t, unsigned int);

template <std::size_t N, typename T>
typename n_dim_vector_type<T, N>::type n_dim_vector_generator<N, T>(T input_data, unsigned int push_back_times) {
    if (N == 0)
    {
        return std::move(input_data);
    }
    typename n_dim_vector_type<T, N>::type return_data;
    for (size_t loop_number = 0; loop_number < push_back_times; loop_number++)
    {
        return_data.push_back(n_dim_vector_generator<N - 1, T>(input_data, push_back_times));
    }
    return return_data;
}

As a result, I got an error 'return': cannot convert from 'long double' to 'std::vector<std::vector<long double,std::allocator<long double>>,std::allocator<std::vector<long double,std::allocator<long double>>>>' I know that it caused by if (N == 0) block which is as the terminate condition to recursive structure. However, if I tried to edit the terminate condition into separate form.

template <typename T>
inline T n_dim_vector_generator<0, T>(T input_data, unsigned int push_back_times) {
    return std::move(input_data);
}

template <std::size_t N, typename T>
typename n_dim_vector_type<T, N>::type n_dim_vector_generator<N, T>(T input_data, unsigned int push_back_times) {
    typename n_dim_vector_type<T, N>::type return_data;
    for (size_t loop_number = 0; loop_number < push_back_times; loop_number++)
    {
        return_data.push_back(n_dim_vector_generator<N - 1, T>(input_data, push_back_times));
    }
    return return_data;
}

The error 'n_dim_vector_generator': illegal use of explicit template arguments happened. Is there any better solution to this problem?

The develop environment is in Windows 10 1909 with Microsoft Visual Studio Enterprise 2019 Version 16.4.3


回答1:


To achieve your specific mapping of:

auto test_vector = n_dim_vector_generator<2, long double>(2, 3)

to a 3x3 vector filled with 2's, your template can be a bit simpler if you take advantage of this vector constructor:

std::vector<std::vector<T>>(COUNT, std::vector<T>(...))

Since vector is copyable, this will fill COUNT slots with a different copy of the vector. So...

template <size_t N, typename T>
struct n_dim_vector_generator {
    using type = std::vector<typename n_dim_vector_generator<N-1, T>::type>;
    type operator()(T value, size_t size) {
        return type(size, n_dim_vector_generator<N-1, T>{}(value, size));
    }
};

template <typename T>
struct n_dim_vector_generator<0, T> {
    using type = T;
    type operator()(T value, size_t size) {
        return value;
    }
};

usage:

auto test_vector = n_dim_vector_generator<2, long double>{}(2, 3);

Demo: https://godbolt.org/z/eiDAUG


For the record, to address some concerns from the comments, C++ has an idiomatic, initializable, contiguous-memory class equivalent of a multi-dimension C array: a nested std::array:

std::array<std::array<long double, COLUMNS>, ROWS> test_array = { /*...*/ };

for (auto& row : test_array)
    for (auto cell : row)
        std::cout << cell << std::endl;

If you wanted to reduce the boilerplate to declare one, you can use a struct for that:

template <typename T, size_t... N>
struct multi_array;

template <typename T, size_t NFirst, size_t... N>
struct multi_array<T, NFirst, N...> {
    using type = std::array<typename multi_array<T, N...>::type, NFirst>;
};

template <typename T, size_t NLast>
struct multi_array<T, NLast> {
    using type = std::array<T, NLast>;
};

template <typename T, size_t... N>
using multi_array_t = typename multi_array<T, N...>::type;

Then to use:

multi_array_t<long double, ROWS, COLUMNS> test_array = { /*...*/ };

for (auto& row : test_array)
    for (auto cell : row)
        std::cout << cell << std::endl;

This is allocated on the stack, like a C array. That will eat up your stack space for a big array of course. But you can make a decorator range around std::unique_ptr to make a pointer to one a bit easier to access:

template <typename T, size_t... N>
struct dynamic_multi_array : std::unique_ptr<multi_array_t<T, N...>> {
    using std::unique_ptr<multi_array_t<T, N...>>::unique_ptr;
    constexpr typename multi_array_t<T, N...>::value_type& operator [](size_t index) { return (**this)[index]; }
    constexpr const typename multi_array_t<T, N...>::value_type& operator [](size_t index) const { return (**this)[index]; }
    constexpr typename multi_array_t<T, N...>::iterator begin() { return (**this).begin(); }
    constexpr typename multi_array_t<T, N...>::iterator end() { return (**this).end(); }
    constexpr typename multi_array_t<T, N...>::const_iterator begin() const { return (**this).begin(); }
    constexpr typename multi_array_t<T, N...>::const_iterator end() const { return (**this).end(); }
    constexpr typename multi_array_t<T, N...>::const_iterator cbegin() const { return (**this).cbegin(); }
    constexpr typename multi_array_t<T, N...>::const_iterator cend() const { return (**this).cend(); }
    constexpr typename multi_array_t<T, N...>::size_type size() const { return (**this).size(); }
    constexpr bool empty() const { return (**this).empty(); }
    constexpr typename multi_array_t<T, N...>::value_type* data() { return (**this).data(); }
    constexpr const typename multi_array_t<T, N...>::value_type* data() const { return (**this).data(); }
};

(let the buyer beware if you use those methods with nullptr)

Then you can still brace-initialize a new expression and use it like a container:

dynamic_multi_array<long double, ROWS, COLUMNS> test_array {
    new multi_array_t<long double, ROWS, COLUMNS> { /* ... */ }
};

for (auto& row : test_array)
    for (auto cell : row)
        std::cout << cell << std::endl;

Demo: https://godbolt.org/z/lUwVE_



来源:https://stackoverflow.com/questions/59970668/generate-arbitrarily-nested-vectors-in-c

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!