সংকলনের সময় আমি কীভাবে একটি বহুমাত্রিক স্টাড :: ভেক্টরের গভীরতা পেতে পারি?


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
একটি এর আকার std::vectorরান-টাইম জিনিস, সংকলন-সময় নয়। আপনি যদি একটি সংকলন-সময়ের আকারের ধারক চান তবে দেখুন std::array। এছাড়াও; মনে রাখবেন যে constexprকেবলমাত্র " সঙ্কলনের সময় মূল্যায়ন করা যেতে পারে" - এর কোনও প্রতিশ্রুতি থাকবে না। এটি রান-টাইমে মূল্যায়ন করা যেতে পারে।
জেস্পার জুহল

5
@ জেস্পার জুহল, আমি আকার খুঁজছি না, আমি গভীরতা খুঁজছি। দুটি খুব আলাদা জিনিস। আমি জানতে চাই যে কতজন std::vectorএকে অপরের মধ্যে বাসা বেঁধেছে। উদাহরণস্বরূপ std::vector<std::vector<int>> v;, GetDepth(v);এটি 2 মাত্রিক ভেক্টর হওয়ায় 2 ফিরে আসবে। আকারটি অপ্রাসঙ্গিক।
tjwrona1992

4
আধা-সম্পর্কিত: নেস্টেড vectorসবসময় জিনিস করার সর্বোত্তম উপায় নয়। একক ফ্ল্যাট ভেক্টরের ম্যানুয়াল 2 ডি বা 3 ডি ইনডেক্সিং ব্যবহারের ক্ষেত্রে নির্ভর করে আরও দক্ষ হতে পারে। (বাইরের স্তরগুলি থেকে পয়েন্টার-তাড়া না করে কেবল পূর্ণসংখ্যার গণিত))
পিটার

1
@ পিটারকর্ডস আরও দক্ষতা কেবল একটি দিক t আর একটি হ'ল একটি ফ্ল্যাট প্রকারটি আরও ভাল অ্যারের সংলগ্ন প্রকৃতির প্রতিনিধিত্ব করে। একটি নেস্টেড স্ট্রাকচার (সম্ভাব্য পৃথক দৈর্ঘ্যের পৃথক পৃথক দৈর্ঘ্যের) মূলত একটি সংলগ্ন, এন-ডাইমেনশনাল হাইপারসেক্টাঙ্গলের প্রতিনিধিত্ব করার জন্য এক প্রকারের অমিল।
কনরাড রুডল্ফ

4
নামকরণ-ভিত্তিক স্ট্যান্ডার্ড লাইব্রেরি rankএই ক্যোয়ারির জন্য অ্যারে প্রকারের (টেনারগুলির জন্য গাণিতিক নামকরণের সাথে চুক্তিতে) ব্যবহার করে। সম্ভবত এটি "গভীরতা" এর চেয়ে ভাল শব্দ।
ডিএমকেকে --- প্রাক্তন মডারেটর বিড়ালছানা

উত্তর:


48

একটি ক্লাসিক টেম্প্লেটিং সমস্যা। সি ++ স্ট্যান্ডার্ড লাইব্রেরি কীভাবে করে তার মতো একটি সাধারণ সমাধান এখানে। প্রাথমিক ধারণাটি একটি পুনরাবৃত্তাকার টেম্পলেট রয়েছে যা ভেক্টর নয় এমন কোনও ধরণের জন্য 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::beginADL লুকআপ জন্য আমদানি ও হয় 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 ওহ, এর জন্য আপনাকে SFINAE কনস্টেক্সপ্র ফাংশনগুলি ব্যবহার করতে হবে। আমি এটির জন্য একটি জিনিস প্রোটোটাইপ করব এবং এটি সম্পাদনা হিসাবে যুক্ত করব।
ক্রুজ জিন

@ tjwrona1992, একটি ধারক সম্পর্কে আপনার সংজ্ঞা কি?
ইভ

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
@ ক্রুজজিয়ান, নিশ্চিত এজন্য আমি জিজ্ঞাসা করেছি একটি ধারক কী। যাইহোক, এটি অতিরিক্ত বিশেষজ্ঞের সাথে সহজেই ঠিক করা যেতে পারে, আপডেট হওয়া উত্তরটি দেখুন see
ইভ

2
@ এভিজি আপনাকে ধন্যবাদ আজ আমি শিখেছি স্টাডি :: অকার্যকর_আর সম্পর্কে! উজ্জ্বল!
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&)এবং শুধু ফিরে ভাল হবে vector_depth<T>::valuevolatileসর্বাধিক সিভি যোগ্য হওয়া মাত্র এটি আরও স্টাফগুলি কভার করতে দেয়
ক্রুজ জিন

@ ক্রুজজিয়ান পরামর্শের জন্য ধন্যবাদ। আমি উত্তর সম্পাদনা করেছি।
り ネ ロ
আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.