Làm thế nào tôi có thể có được độ sâu của một vectơ std :: chiều đa chiều tại thời gian biên dịch?


45

Tôi có một hàm có nhiều chiều std::vectorvà yêu cầu độ sâu (hoặc số lượng kích thước) được truyền vào dưới dạng tham số mẫu. Thay vì mã hóa giá trị này, tôi muốn viết một constexprhàm sẽ lấy std::vectorvà trả về độ sâu làm unsigned integergiá trị.

Ví dụ:

std::vector<std::vector<std::vector<int>>> v =
{
    { { 0, 1}, { 2, 3 } },
    { { 4, 5}, { 6, 7 } },
};

// Returns 3
size_t depth = GetDepth(v);

Điều này cần phải được thực hiện tại thời điểm biên dịch bởi vì độ sâu này sẽ được chuyển đến hàm mẫu dưới dạng tham số mẫu:

// Same as calling foo<3>(v);
foo<GetDepth(v)>(v);

Có cách nào để làm điều này?


4
Kích thước của một std::vectorlà một thời gian chạy, không phải là một thời gian biên dịch. Nếu bạn muốn một thùng chứa kích thước thời gian biên dịch, hãy tìm đến std::array. Cũng thế; hãy nhớ rằng constexprchỉ có nghĩa là " có thể được đánh giá tại thời điểm biên dịch" - không có hứa hẹn rằng nó sẽ được. Nó có thể được đánh giá tại thời gian chạy.
Jesper Juhl

5
@JesperJuhl, tôi không tìm kiếm kích thước, tôi đang tìm kiếm chiều sâu. Hai điều rất khác nhau. Tôi muốn biết có bao nhiêu std::vectors được lồng vào nhau. Ví dụ với std::vector<std::vector<int>> v;, GetDepth(v);sẽ trả về 2 vì nó là vectơ 2 chiều. Kích thước không liên quan.
tjwrona1992

4
Liên quan đến bán: lồng nhau vectorkhông phải luôn là cách tốt nhất để làm việc. Hướng dẫn lập chỉ mục 2d hoặc 3d của một vectơ phẳng đơn có thể hiệu quả hơn, tùy thuộc vào trường hợp sử dụng. (Chỉ là toán số nguyên thay vì đuổi theo con trỏ từ các cấp độ bên ngoài.)
Peter Cordes

1
@PeterCordes Hiệu quả tốt hơn chỉ là một khía cạnh. Một điều nữa là một loại phẳng thể hiện tốt hơn tính chất tiếp giáp của mảng. Một cấu trúc lồng nhau (có độ dài riêng biệt có khả năng khác nhau) về cơ bản là một kiểu không phù hợp để đại diện cho một siêu hình chữ nhật n chiều liền kề.
Konrad Rudolph

4
Danh pháp thông minh mà thư viện chuẩn sử dụng rankcho truy vấn này trên các kiểu mảng (phù hợp với danh pháp toán học cho các thang đo). Có lẽ đó là một từ tốt hơn ở đây hơn là "độ sâu".
dmckee --- ex-moderator mèo con

Câu trả lời:


48

Một vấn đề templating cổ điển. Đây là một giải pháp đơn giản như cách thư viện chuẩn C ++ thực hiện. Ý tưởng cơ bản là có một mẫu đệ quy sẽ đếm từng cái một, với trường hợp cơ bản là 0 cho bất kỳ loại nào không phải là vectơ.

#include <vector>
#include <type_traits>

template<typename T>
struct dimensions : std::integral_constant<std::size_t, 0> {};

template<typename T>
struct dimensions<std::vector<T>> : std::integral_constant<std::size_t, 1 + dimensions<T>::value> {};

template<typename T>
inline constexpr std::size_t dimensions_v = dimensions<T>::value; // (C++17)

Vì vậy, sau đó bạn có thể sử dụng nó như vậy:

dimensions<vector<vector<vector<int>>>>::value; // 3
// OR
dimensions_v<vector<vector<vector<int>>>>; // also 3 (C++17)

Biên tập:

Ok, tôi đã hoàn thành việc triển khai chung cho bất kỳ loại container nào. Lưu ý rằng tôi đã xác định một loại container như bất cứ điều gì mà có một loại iterator tốt được hình thành theo các biểu hiện begin(t)nơi std::beginđược nhập khẩu để tra cứu ADL và tlà một vế trái của loại T.

Đây là mã của tôi cùng với các bình luận để giải thích lý do tại sao công cụ hoạt động và các trường hợp thử nghiệm tôi đã sử dụng. Lưu ý, điều này đòi hỏi C ++ 17 để biên dịch.

#include <iostream>
#include <vector>
#include <array>
#include <type_traits>

using std::begin; // import std::begin for handling C-style array with the same ADL idiom as the other types

// decide whether T is a container type - i define this as anything that has a well formed begin iterator type.
// we return true/false to determing if T is a container type.
// we use the type conversion ability of nullptr to std::nullptr_t or void* (prefers std::nullptr_t overload if it exists).
// use SFINAE to conditionally enable the std::nullptr_t overload.
// these types might not have a default constructor, so return a pointer to it.
// base case returns void* which we decay to void to represent not a container.
template<typename T>
void *_iter_elem(void*) { return nullptr; }
template<typename T>
typename std::iterator_traits<decltype(begin(*(T*)nullptr))>::value_type *_iter_elem(std::nullptr_t) { return nullptr; }

// this is just a convenience wrapper to make the above user friendly
template<typename T>
struct container_stuff
{
    typedef std::remove_pointer_t<decltype(_iter_elem<T>(nullptr))> elem_t;    // the element type if T is a container, otherwise void
    static inline constexpr bool is_container = !std::is_same_v<elem_t, void>; // true iff T is a container
};

// and our old dimension counting logic (now uses std:nullptr_t SFINAE logic)
template<typename T>
constexpr std::size_t _dimensions(void*) { return 0; }

template<typename T, std::enable_if_t<container_stuff<T>::is_container, int> = 0>
constexpr std::size_t _dimensions(std::nullptr_t) { return 1 + _dimensions<typename container_stuff<T>::elem_t>(nullptr); }

// and our nice little alias
template<typename T>
inline constexpr std::size_t dimensions_v = _dimensions<T>(nullptr);

int main()
{
    std::cout << container_stuff<int>::is_container << '\n';                 // false
    std::cout << container_stuff<int[6]>::is_container<< '\n';               // true
    std::cout << container_stuff<std::vector<int>>::is_container << '\n';    // true
    std::cout << container_stuff<std::array<int, 3>>::is_container << '\n';  // true
    std::cout << dimensions_v<std::vector<std::array<std::vector<int>, 2>>>; // 3
}

Điều gì sẽ xảy ra nếu tôi muốn nó hoạt động cho tất cả các container lồng nhau và không chỉ các vectơ? Có một cách dễ dàng để thực hiện điều đó?
tjwrona1992

@ tjwrona1992 Ya, bạn có thể chỉ cần sao chép và dán std::vector<T>chuyên môn và thay đổi nó thành một số loại container khác. Điều duy nhất bạn cần là trường hợp cơ sở 0 cho bất kỳ loại nào bạn không chuyên
Cruz Jean

Ý tôi là không sao chép / dán haha, Giống như tạo khuôn mẫu
tjwrona1992

@ tjwrona1992 Ồ, vì bạn sẽ cần phải sử dụng các hàm constexpr SFINAE. Tôi sẽ tạo nguyên mẫu cho điều đó và thêm nó dưới dạng chỉnh sửa.
Cruz Jean

@ tjwrona1992, định nghĩa của bạn về một container là gì?
Evg

15

Giả sử rằng một container là bất kỳ loại nào có value_typeiteratorcác loại thành viên (các thùng chứa thư viện tiêu chuẩn đáp ứng yêu cầu này) hoặc mảng kiểu C, chúng ta có thể dễ dàng khái quát hóa giải pháp của Cruz Jean :

template<class T, typename = void>
struct rank : std::integral_constant<std::size_t, 0> {};

// C-style arrays
template<class T>
struct rank<T[], void> 
    : std::integral_constant<std::size_t, 1 + rank<T>::value> {};

template<class T, std::size_t n>
struct rank<T[n], void> 
    : std::integral_constant<std::size_t, 1 + rank<T>::value> {};

// Standard containers
template<class T>
struct rank<T, std::void_t<typename T::iterator, typename T::value_type>> 
    : std::integral_constant<std::size_t, 1 + rank<typename T::value_type>::value> {};

int main() {
    using T1 = std::list<std::set<std::array<std::vector<int>, 4>>>;
    using T2 = std::list<std::set<std::vector<int>[4]>>;

    std::cout << rank<T1>();  // Output : 4
    std::cout << rank<T2>();  // Output : 4
}

Các loại container có thể được hạn chế hơn nữa nếu cần thiết.


Điều này không hoạt động đối với mảng kiểu C
Cruz Jean

1
@CruzJean, chắc chắn rồi. Đó là lý do tại sao tôi hỏi một container là gì. Dù sao, nó có thể dễ dàng được sửa chữa với một chuyên ngành bổ sung, xem câu trả lời cập nhật.
Evg

2
@Evg cảm ơn bạn. Hôm nay tôi đã học về std :: void_t! Xuất sắc!
marco6

2

Bạn có thể định nghĩa mẫu lớp sau vector_depth<>phù hợp với bất kỳ loại nào:

template<typename T>
struct vector_depth {
   static constexpr size_t value = 0;
};

Mẫu chính này tương ứng với trường hợp cơ sở kết thúc đệ quy. Sau đó, xác định chuyên môn tương ứng của nó chostd::vector<T> :

template<typename T>
struct vector_depth<std::vector<T>> {
   static constexpr size_t value = 1 + vector_depth<T>::value;
};

Chuyên môn này phù hợp với một std::vector<T>và tương ứng với trường hợp đệ quy.

Cuối cùng, định nghĩa mẫu hàm GetDepth(),, dùng cho mẫu lớp ở trên:

template<typename T>
constexpr auto GetDepth(T&&) {
   return vector_depth<std::remove_cv_t<std::remove_reference_t<T>>>::value;
}

Thí dụ:

auto main() -> int {
   int a{}; // zero depth
   std::vector<int> b;
   std::vector<std::vector<int>> c;
   std::vector<std::vector<std::vector<int>>> d;

   // constexpr - dimension determinted at compile time
   constexpr auto depth_a = GetDepth(a);
   constexpr auto depth_b = GetDepth(b);
   constexpr auto depth_c = GetDepth(c);
   constexpr auto depth_d = GetDepth(d);

   std::cout << depth_a << ' ' << depth_b << ' ' << depth_c << ' ' << depth_d;
}

Đầu ra của chương trình này là:

0 1 2 3

1
công trình này cho std::vector, nhưng ví dụ như GetDepth(v)nơi vđược intsẽ không biên dịch. Sẽ tốt hơn để có GetDepth(const volatile T&)và chỉ cần trở lại vector_depth<T>::value. volatilechỉ cho phép nó bao gồm nhiều thứ hơn, đủ điều kiện cv tối đa
Cruz Jean

@CruzJean Cảm ơn bạn đã gợi ý. Tôi đã chỉnh sửa câu trả lời.
眠 り ネ ロ
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.