मैं एक बहुआयामी एसटीडी की गहराई कैसे प्राप्त कर सकता हूं :: संकलन समय पर वेक्टर?


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केवल इसका मतलब है " संकलन समय पर मूल्यांकन किया जा सकता है" - कोई वादा नहीं है कि यह होगा । इसका मूल्यांकन रन-टाइम पर किया जा सकता है।
जेस्पर जुहल

5
@ जेस्परजुहल, मैं आकार की तलाश में नहीं हूं, मैं गहराई की तलाश कर रहा हूं। दो बहुत अलग चीजें। मैं जानना चाहता हूं कि std::vectorएक दूसरे के भीतर कितने नेस्टेड हैं। उदाहरण के लिए std::vector<std::vector<int>> v;, GetDepth(v);2 लौटेगा क्योंकि यह एक 2 आयामी वेक्टर है। आकार अप्रासंगिक है।
tjwrona1992 16

4
अर्ध-संबंधित: नेस्टेड vectorहमेशा चीजों को करने का सबसे अच्छा तरीका नहीं है। उपयोग के मामले के आधार पर, एकल फ्लैट वेक्टर के मैनुअल 2 डी या 3 डी इंडेक्सिंग अधिक कुशल हो सकते हैं। (बाहरी स्तरों से पॉइंटर-चेज़िंग के बजाय सिर्फ पूर्णांक गणित।)
पीटर कॉर्ड्स

1
@PeterCordes बेहतर दक्षता सिर्फ एक पहलू है। एक और यह है कि एक फ्लैट प्रकार बेहतर सरणी के सन्निहित प्रकृति का प्रतिनिधित्व करता है। एक नेस्टेड संरचना (संभावित अलग-अलग लंबाई की) मूल रूप से एक सन्निहित, एन-आयामी हाइपरट्रैंगल का प्रतिनिधित्व करने के लिए एक प्रकार का बेमेल है।
कोनराड रूडोल्फ

4
नामकरण-वार मानक पुस्तकालय rankसरणी प्रकारों पर इस क्वेरी के लिए उपयोग करता है (दसियों के लिए गणितीय नामकरण के साथ समझौते में)। शायद यह "गहराई" से बेहतर शब्द है।
dmckee --- पूर्व-मध्यस्थ ने बिल्ली का बच्चा

जवाबों:


48

एक क्लासिक अस्थायी समस्या। यहाँ एक सरल उपाय है जैसे कि 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)

संपादित करें:

ठीक है, मैंने किसी भी कंटेनर प्रकार के लिए सामान्य कार्यान्वयन समाप्त कर लिया है। ध्यान दें कि मैंने एक कंटेनर प्रकार को कुछ भी परिभाषित किया है जिसमें अभिव्यक्ति के अनुसार एक अच्छी तरह से गठित इट्रेटर प्रकार है begin(t)जहां std::beginएडीएल लुकअप के लिए आयात किया tजाता है और यह प्रकार का एक प्रकार है 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 ओह, इसके लिए आपको SFINAE कॉन्स्ट्रेक्प फ़ंक्शन का उपयोग करना होगा। मैं इसके लिए एक चीज़ का प्रोटोटाइप बनाऊंगा और इसे एडिट के रूप में जोड़ूंगा।
क्रूज जीन

@ tjwrona1992, कंटेनर की आपकी परिभाषा क्या है?
Evg

15

यह मानते हुए कि एक कंटेनर किसी भी प्रकार का है value_typeऔर iteratorसदस्य प्रकार (मानक पुस्तकालय कंटेनर इस आवश्यकता को पूरा करते हैं) या एक सी-शैली की सरणी है, हम आसानी से क्रूज़ जीन के समाधान को सामान्य कर सकते हैं:

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
}

यदि आवश्यक हो तो कंटेनर प्रकार को और प्रतिबंधित किया जा सकता है।


यह सी-स्टाइल सरणियों के लिए काम नहीं करता है
क्रूज़ जीन

1
@ क्रूज़, निश्चित। इसलिए मैंने पूछा कि कंटेनर क्या है। वैसे भी, यह आसानी से एक अतिरिक्त विशेषज्ञता के साथ तय किया जा सकता है, अपडेट किया गया जवाब देखें।
Evg

2
@ ईवीजी धन्यवाद। आज मैंने std :: void_t के बारे में सीखा! प्रतिभाशाली!
मार्को 6

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&)बस लौट आएं vector_depth<T>::valuevolatileबस इसे और अधिक सामान को कवर करने देता है, अधिकतम cv योग्य होने के नाते
क्रूज़ जीन

@CruzJean सुझाव के लिए धन्यवाद। मैंने उत्तर संपादित किया है।
'ネ ロ 19
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.