ฉันจะรับความลึกของ std หลายมิติ :: เวกเตอร์ในเวลารวบรวมได้อย่างไร


45

ฉันมีฟังก์ชั่นที่ใช้หลายมิติstd::vectorและต้องการความลึก (หรือจำนวนมิติ) ที่จะส่งผ่านเป็นพารามิเตอร์เทมเพลต แทนที่จะเขียนโค้ดนี้ฉันอยากจะเขียนconstexprฟังก์ชั่นที่จะรับstd::vectorและคืนความลึกเป็นunsigned integerค่า

ตัวอย่างเช่น:

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

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

สิ่งนี้จะต้องทำในเวลารวบรวมแต่เนื่องจากความลึกนี้จะถูกส่งผ่านไปยังฟังก์ชันเทมเพลตเป็นพารามิเตอร์เทมเพลต:

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

มีวิธีการทำเช่นนี้?


4
ขนาดของ a std::vectorเป็นสิ่งที่ต้องทำไม่ใช่เวลารวบรวม std::arrayหากคุณต้องการภาชนะขนาดรวบรวมเวลามองไปที่ นอกจากนี้ยัง; โปรดจำไว้ว่าconstexprหมายถึงเท่านั้น " อาจถูกประเมิน ณ เวลารวบรวม" - ไม่มีสัญญาว่าจะเป็น มันอาจถูกประเมิน ณ รันไทม์
Jesper Juhl

5
@JesperJuhl ฉันไม่ได้มองหาขนาดฉันกำลังมองหาความลึก สองสิ่งที่แตกต่างกันมาก ฉันต้องการทราบจำนวนstd::vectorซ้อนกันภายใน ตัวอย่างเช่นมีstd::vector<std::vector<int>> v;, GetDepth(v);จะกลับมา 2 เนื่องจากเป็นเวกเตอร์ 2 มิติ ขนาดไม่เกี่ยวข้อง
tjwrona1992

4
กึ่งเกี่ยวข้อง: ซ้อนกันvectorไม่ใช่วิธีที่ดีที่สุดในการทำสิ่งต่าง ๆ เสมอ การจัดทำดัชนีแบบ 2d หรือ 3d ด้วยตนเองของเวกเตอร์แบบแบนเดียวสามารถมีประสิทธิภาพมากขึ้นขึ้นอยู่กับกรณีการใช้ (เพียงแค่เลขจำนวนเต็มแทนการไล่ล่าตัวชี้จากระดับนอก)
Peter Cordes

1
@PeterCordes ประสิทธิภาพที่ดีขึ้นเป็นเพียงหนึ่งแง่มุม อีกชนิดหนึ่งคือประเภทแฟลตที่ดีกว่าแสดงถึงลักษณะที่ต่อเนื่องของอาเรย์ โครงสร้างที่ซ้อนกัน (ความยาวของแต่ละบุคคลที่อาจแตกต่างกัน) เป็นพื้นฐานที่ไม่ตรงกันประเภทสำหรับการแสดงไฮเปอร์แพรคติกแบบมิติ n ติดกัน
Konrad Rudolph

4
Nomenclature-wise ไลบรารีมาตรฐานใช้rankสำหรับเคียวรีนี้กับชนิดอาร์เรย์ (ตามข้อตกลงกับการตั้งชื่อทางคณิตศาสตร์สำหรับเทนเซอร์) บางทีนั่นอาจเป็นคำที่ดีกว่า "ความลึก"
dmckee --- ผู้ดูแลอดีตลูกแมว

คำตอบ:


48

ปัญหา templating คลาสสิก ต่อไปนี้เป็นวิธีแก้ปัญหาง่ายๆเช่นเดียวกับที่ห้องสมุดมาตรฐาน C ++ ทำ แนวคิดพื้นฐานคือการมีเทมเพลตแบบเรียกซ้ำที่จะนับทีละมิติโดยมีกรณีพื้นฐานเป็น 0 สำหรับประเภทใด ๆ ที่ไม่ใช่เวกเตอร์

#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)

ดังนั้นคุณสามารถใช้มันได้เช่น:

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

แก้ไข:

ตกลงฉันได้ทำการติดตั้งทั่วไปสำหรับคอนเทนเนอร์ประเภทใดแล้ว หมายเหตุที่ผมกำหนดประเภทภาชนะเป็นอะไรที่มีชนิด iterator ดีขึ้นตามการแสดงออกbegin(t)ที่std::beginถูกนำเข้าสำหรับการค้นหา ADL และtเป็น lvalue Tประเภท

นี่คือรหัสของฉันพร้อมกับความคิดเห็นเพื่ออธิบายว่าทำไมสิ่งต่าง ๆ ถึงใช้งานได้และกรณีทดสอบที่ฉันใช้ หมายเหตุสิ่งนี้ต้องใช้ C ++ 17 ในการรวบรวม

#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
}

ถ้าฉันต้องการให้มันทำงานกับภาชนะซ้อนกันทั้งหมดและไม่ใช่แค่เวกเตอร์ล่ะ? มีวิธีง่าย ๆ ในการทำให้เกิดขึ้นหรือไม่?
tjwrona1992

@ tjwrona1992 ใช่คุณสามารถคัดลอกและวางstd::vector<T>ความเชี่ยวชาญและเปลี่ยนเป็นประเภทคอนเทนเนอร์อื่นได้ สิ่งเดียวที่คุณต้องการคือเคสพื้นฐาน 0 สำหรับประเภทที่คุณไม่เชี่ยวชาญ
ครูซฌอง

ฉันหมายถึงโดยไม่ต้องคัดลอก / วางฮ่าฮ่า ๆ เทมเพลตเทมเพลต
tjwrona1992

@ tjwrona1992 โอ้คุณต้องใช้ฟังก์ชัน constexpr ของ SFINAE ฉันจะสร้างต้นแบบสำหรับสิ่งนั้นและเพิ่มเป็นการแก้ไข
Cruz Jean

@ tjwrona1992 คำจำกัดความของคอนเทนเนอร์คืออะไร
Evg

15

สมมติว่าคอนเทนเนอร์เป็นชนิดใด ๆ ที่มีvalue_typeและiteratorสมาชิก (คอนเทนเนอร์ไลบรารีมาตรฐานตรงตามข้อกำหนดนี้) หรืออาร์เรย์สไตล์ C เราสามารถพูดคุยกับโซลูชันของ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
Cruz Jean

1
@CruzJean แน่นอน นั่นเป็นเหตุผลที่ฉันถามว่าคอนเทนเนอร์คืออะไร อย่างไรก็ตามมันสามารถแก้ไขได้อย่างง่ายดายด้วยความเชี่ยวชาญเพิ่มเติมดูคำตอบที่ปรับปรุง
Evg

2
@Evg ขอบคุณ วันนี้ฉันเรียนรู้เกี่ยวกับ std :: void_t! ยอดเยี่ยม!
marco6

2

คุณสามารถกำหนดเทมเพลตคลาสต่อไปนี้vector_depth<>ซึ่งตรงกับประเภทใดก็ได้:

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

แม่แบบหลักนี้สอดคล้องกับกรณีฐานที่สิ้นสุดการสอบถามซ้ำ จากนั้นกำหนดความเชี่ยวชาญที่สอดคล้องกันสำหรับstd::vector<T>:

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

ความเชี่ยวชาญนี้ตรงกับstd::vector<T>และตรงกับกรณีที่เกิดซ้ำ

สุดท้ายกำหนดเทมเพลตฟังก์ชันGetDepth()ซึ่งจะใช้กับเทมเพลตชั้นเรียนด้านบน:

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

ตัวอย่าง:

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;
}

ผลลัพธ์ของโปรแกรมนี้คือ:

0 1 2 3

1
งานนี้ได้std::vectorแต่เช่นGetDepth(v)ที่vเป็นintจะไม่รวบรวม จะดีกว่าที่จะมีและเพิ่งกลับมาGetDepth(const volatile T&) เพียงแค่ให้มันครอบคลุมเนื้อหามากขึ้นโดยมีคุณสมบัติสูงสุด cvvector_depth<T>::valuevolatile
Cruz Jean

@CruzJean ขอบคุณสำหรับคำแนะนำ ฉันแก้ไขคำตอบแล้ว
眠りネロク
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.