উত্তর:
আমি মনে করি যে এমন প্যারামিটারটি পাস করার জন্য আপনাকে টেমপ্লেট টেমপ্লেট সিনট্যাক্স ব্যবহার করতে হবে যার প্রকারের মতো অন্য টেম্পলেটের উপর নির্ভরশীল একটি টেম্পলেট:
template <template<class> class H, class S>
void f(const H<S> &value) {
}
এখানে, H
একটি টেমপ্লেট, তবে আমি এই ফাংশনটি সমস্ত বিশেষত্বের সাথে ডিল করতে চেয়েছিলাম H
।
দ্রষ্টব্য : আমি বহু বছর ধরে সি ++ প্রোগ্রামিং করে চলেছি এবং কেবল এটির জন্য একবারের দরকার পড়ে। আমি দেখতে পেলাম যে এটি খুব কমই প্রয়োজনীয় বৈশিষ্ট্য (অবশ্যই যখন আপনার প্রয়োজন হবে তখন কার্যকর!)।
আমি ভাল উদাহরণগুলি চিন্তা করার চেষ্টা করেছি, এবং সত্যি বলতে, বেশিরভাগ সময় এটি প্রয়োজন হয় না, তবে আসুন একটি উদাহরণ তৈরি করি। এর সাজা করা যাক যে std::vector
নেই একটি আছে typedef value_type
।
সুতরাং আপনি কীভাবে কোনও ফাংশন লিখবেন যা ভেক্টর উপাদানগুলির জন্য সঠিক ধরণের ভেরিয়েবল তৈরি করতে পারে? এটি কাজ করবে।
template <template<class, class> class V, class T, class A>
void f(V<T, A> &v) {
// This can be "typename V<T, A>::value_type",
// but we are pretending we don't have it
T temp = v.back();
v.pop_back();
// Do some work on temp
std::cout << temp << std::endl;
}
দ্রষ্টব্য : std::vector
দুটি টেম্পলেট প্যারামিটার, টাইপ, এবং বরাদ্দকারী রয়েছে, তাই আমাদের উভয়টিকেই গ্রহণ করতে হয়েছিল। ভাগ্যক্রমে, টাইপ ছাড়ের কারণে, আমাদের সঠিক টাইপটি স্পষ্টভাবে লিখতে হবে না।
যা আপনি এটির মতো ব্যবহার করতে পারেন:
f<std::vector, int>(v); // v is of type std::vector<int> using any allocator
বা আরও ভাল, আমরা কেবল ব্যবহার করতে পারি:
f(v); // everything is deduced, f can deal with a vector of any type!
আপডেট : এমনকি এই সংকীর্ণ উদাহরণটি উদাহরণস্বরূপ, সি ++ 11 প্রবর্তনের কারণে আর আশ্চর্যজনক উদাহরণ নয় auto
। এখন একই ফাংশন হিসাবে লেখা যেতে পারে:
template <class Cont>
void f(Cont &v) {
auto temp = v.back();
v.pop_back();
// Do some work on temp
std::cout << temp << std::endl;
}
যা আমি এই জাতীয় কোড লিখতে পছন্দ করব।
template<template<class, class> class C, class T, class U> void f(C<T, U> &v)
f<vector,int>
এবং না f<vector<int>>
।
f<vector,int>
অর্থ f<ATemplate,AType>
, f<vector<int>>
অর্থf<AType>
আসলে, টেমপ্লেট টেমপ্লেট পরামিতিগুলির জন্য ইউজকেস বরং স্পষ্ট। একবার আপনি যখন জানতে পারবেন যে সি ++ স্ট্ডলিবের স্ট্যান্ড আউটপুট অপারেটরগুলিকে স্ট্যান্ডার্ড ধারক প্রকারের জন্য সংজ্ঞায়িত না করার ফাঁক গর্ত রয়েছে, আপনি এরকম কিছু লেখার জন্য এগিয়ে যাবেন:
template<typename T>
static inline std::ostream& operator<<(std::ostream& out, std::list<T> const& v)
{
out << '[';
if (!v.empty()) {
for (typename std::list<T>::const_iterator i = v.begin(); ;) {
out << *i;
if (++i == v.end())
break;
out << ", ";
}
}
out << ']';
return out;
}
তারপরে আপনি সনাক্ত করতে পারেন যে ভেক্টরের জন্য কোডটি ঠিক একই, ফরওয়ার্ড_লিস্ট একই, আসলে, এমনকি অনেকগুলি মানচিত্রের জন্য এখনও এটি ঠিক একই। এই টেম্পলেট শ্রেণীর মেটা-ইন্টারফেস / প্রোটোকল ব্যতীত সাধারণ কিছু নেই এবং টেমপ্লেট টেমপ্লেট প্যারামিটার ব্যবহার করে সেগুলির মধ্যে সমস্ত সাধারণতা ক্যাপচার করতে দেয়। যদিও কোনও টেমপ্লেট লিখতে যাওয়ার আগে, মানের ধরণ এবং বরাদ্দকারীর জন্য - ক্রম ধারক দুটি টেম্পলেট আর্গুমেন্ট গ্রহণ করে তা স্মরণ করিয়ে দেওয়ার জন্য একটি রেফারেন্স চেক করা মূল্য। যখন বরাদ্দকারীকে খেলাপি করা হয়, তবুও আমাদের টেম্পলেট অপারেটরে এর অস্তিত্বের জন্য আমাদের অ্যাকাউন্ট করা উচিত <<:
template<template <typename, typename> class Container, class V, class A>
std::ostream& operator<<(std::ostream& out, Container<V, A> const& v)
...
ভয়েলা, এটি স্ট্যান্ডার্ড প্রোটোকলটি মেনে চলার বর্তমান এবং ভবিষ্যতের সমস্ত ক্রমের ধারকগুলির জন্য স্বয়ংক্রিয়ভাবে কাজ করবে। মিশ্রণে মানচিত্র যুক্ত করতে, তারা 4 টি টেমপ্লেট প্যারাম গ্রহণ করে তা উল্লেখ করার জন্য এটি বিশদভাবে নেওয়া উচিত, সুতরাং আমাদের অপারেটরের << উপরের 4-আরগ টেম্পলেট টেম্পলেট প্যারামের সাথে অন্য সংস্করণ প্রয়োজন। আমরা এটিও দেখতে চাই যে স্ট্যান্ড: জোড়টি 2-আর্গ অপারেটরের সাথে রেন্ডার করার চেষ্টা করে << আমরা পূর্বে নির্ধারিত সিকোয়েন্স টাইপের জন্য, সুতরাং আমরা কেবলমাত্র স্ট্যান্ড :: জোড়ের জন্য একটি বিশেষায়িতকরণ সরবরাহ করব।
বিটিডব্লিউ, সি + ১১ দিয়ে যা ভেরিয়াদিক টেম্পলেটগুলিকে অনুমতি দেয় (এবং এর ফলে ভের্যাদিক টেম্পলেট টেম্পলেট আরোগুলিকে অনুমতি দেওয়া উচিত), একক অপারেটর থাকা সম্ভব হবে << সেগুলি সমস্তই নিয়ন্ত্রণ করতে পারে। উদাহরণ স্বরূপ:
#include <iostream>
#include <vector>
#include <deque>
#include <list>
template<typename T, template<class,class...> class C, class... Args>
std::ostream& operator <<(std::ostream& os, const C<T,Args...>& objs)
{
os << __PRETTY_FUNCTION__ << '\n';
for (auto const& obj : objs)
os << obj << ' ';
return os;
}
int main()
{
std::vector<float> vf { 1.1, 2.2, 3.3, 4.4 };
std::cout << vf << '\n';
std::list<char> lc { 'a', 'b', 'c', 'd' };
std::cout << lc << '\n';
std::deque<int> di { 1, 2, 3, 4 };
std::cout << di << '\n';
return 0;
}
আউটপুট
std::ostream &operator<<(std::ostream &, const C<T, Args...> &) [T = float, C = vector, Args = <std::__1::allocator<float>>]
1.1 2.2 3.3 4.4
std::ostream &operator<<(std::ostream &, const C<T, Args...> &) [T = char, C = list, Args = <std::__1::allocator<char>>]
a b c d
std::ostream &operator<<(std::ostream &, const C<T, Args...> &) [T = int, C = deque, Args = <std::__1::allocator<int>>]
1 2 3 4
__PRETTY_FUNCTION__
, যা অন্যান্য বিষয়গুলির মধ্যেও, সরল পাঠ্যে টেমপ্লেট প্যারামিটারের বিবরণ জানায়। ঝাঁকুনি পাশাপাশি এটি করে। একটি খুব সুবিধাজনক বৈশিষ্ট্য কখনও কখনও (আপনি দেখতে পারেন)।
এন্ড্রেই আলেকজান্দ্রেস্কু কর্তৃক 'মডার্ন সি ++ ডিজাইন - জেনেরিক প্রোগ্রামিং এবং ডিজাইনের প্যাটার্ন প্রয়োগ করা' থেকে নেওয়া একটি সাধারণ উদাহরণ এখানে :
নীতি প্যাটার্নটি প্রয়োগ করতে তিনি টেমপ্লেট টেম্পলেট পরামিতি সহ একটি ক্লাস ব্যবহার করেন:
// Library code
template <template <class> class CreationPolicy>
class WidgetManager : public CreationPolicy<Widget>
{
...
};
তিনি ব্যাখ্যা করেছেন: সাধারণত, হোস্ট ক্লাস ইতিমধ্যে নীতি শ্রেণীর টেম্পলেট যুক্তি জানে বা সহজেই অনুমান করতে পারে। উপরের উদাহরণে, উইজেটম্যানেজার সর্বদা উইজেট টাইপের অবজেক্টগুলিকে পরিচালনা করে, তাই ক্রিয়েশনপোলিসির ইনস্ট্যান্টেশনে ব্যবহারকারীকে আবার উইজেট নির্দিষ্ট করা আবশ্যক এবং অপ্রয়োজনীয় বিপজ্জনক this এই ক্ষেত্রে, লাইব্রেরি কোড নীতি নির্দিষ্ট করার জন্য টেমপ্লেট টেম্পলেট প্যারামিটার ব্যবহার করতে পারে।
এর প্রভাবটি হ'ল ক্লায়েন্ট কোডটি আরও মার্জিত উপায়ে 'উইজেটম্যানেজার' ব্যবহার করতে পারে:
typedef WidgetManager<MyCreationPolicy> MyWidgetMgr;
আরও জটিল, এবং ত্রুটিযুক্ত প্রবণতার পরিবর্তে কোনও সংজ্ঞা টেম্পলেট টেমপ্লেটের আর্গুমেন্টের অভাবের প্রয়োজন হত:
typedef WidgetManager< MyCreationPolicy<Widget> > MyWidgetMgr;
আমার সিইউডিএ কনভলিউশনাল নিউরাল নেটওয়ার্ক লাইব্রেরি থেকে অন্য একটি ব্যবহারিক উদাহরণ এখানে । আমার নিম্নলিখিত ক্লাস টেম্পলেট রয়েছে:
template <class T> class Tensor
যা আসলে এন-ডাইমেনশনাল ম্যাট্রিকেস ম্যানিপুলেশন প্রয়োগ করে। এখানে একটি শিশু শ্রেণির টেম্পলেটও রয়েছে:
template <class T> class TensorGPU : public Tensor<T>
যা একই কার্যকারিতা প্রয়োগ করে তবে জিপিইউতে। উভয় টেমপ্লেটগুলি সমস্ত মূল ধরণের সাথে কাজ করতে পারে, যেমন ভাসা, ডাবল, ইনট ইত্যাদি And
template <template <class> class TT, class T> class CLayerT: public Layer<TT<T> >
{
TT<T> weights;
TT<T> inputs;
TT<int> connection_matrix;
}
এখানে টেমপ্লেট টেমপ্লেট সিনট্যাক্স থাকার কারণ হ'ল আমি শ্রেণীর প্রয়োগের ঘোষণা করতে পারি
class CLayerCuda: public CLayerT<TensorGPU, float>
যার টাইপ ফ্লোট এবং জিপিইউতে ওজন এবং ইনপুট উভয়ই থাকবে, তবে সংযোগ_ম্যাট্রিক্স সর্বদা সিপিইউতে (টিটি = টেনসর উল্লেখ করে) বা জিপিইউতে (টিটি = টেনসরপিপি নির্দিষ্ট করে) থাকবে।
বলুন আপনি শিশু টেম্পলেটগুলির একটি সেট জন্য "ইন্টারফেস" সরবরাহ করতে সিআরটিপি ব্যবহার করছেন; এবং পিতা-মাতা এবং শিশু উভয়ই অন্যান্য টেম্পলেট যুক্তিতে (গুলি) প্যারাম্যাট্রিক ric
template <typename DERIVED, typename VALUE> class interface {
void do_something(VALUE v) {
static_cast<DERIVED*>(this)->do_something(v);
}
};
template <typename VALUE> class derived : public interface<derived, VALUE> {
void do_something(VALUE v) { ... }
};
typedef interface<derived<int>, int> derived_t;
'Int' এর সদৃশটি নোট করুন, যা আসলে উভয় টেমপ্লেটগুলিতে নির্দিষ্ট একই পরামিতি। এই সদৃশটি এড়াতে আপনি ডেরাইভেডের জন্য একটি টেম্পলেট টেম্পলেট ব্যবহার করতে পারেন:
template <template <typename> class DERIVED, typename VALUE> class interface {
void do_something(VALUE v) {
static_cast<DERIVED<VALUE>*>(this)->do_something(v);
}
};
template <typename VALUE> class derived : public interface<derived, VALUE> {
void do_something(VALUE v) { ... }
};
typedef interface<derived, int> derived_t;
নোট করুন যে আপনি সরাসরি উত্পন্ন টেম্পলেটটিতে অন্যান্য টেম্পলেট প্যারামিটারগুলি সরবরাহ করছেন ; "ইন্টারফেস" এখনও তাদের গ্রহণ করে।
এটি আপনাকে "ইন্টারফেস" এ টাইপডেফগুলি তৈরি করতে দেয় যা ধরণের পরামিতিগুলির উপর নির্ভর করে, যা উত্পন্ন টেম্পলেট থেকে অ্যাক্সেসযোগ্য হবে।
উপরের টাইপিডেফ কাজ করে না কারণ আপনি কোনও অনির্ধারিত টেম্পলেটটিতে টাইপ করতে পারবেন না। এটি কাজ করে তবে (এবং সি ++ 11 এর টেম্পলেট টাইপডেফের জন্য স্থানীয় সমর্থন রয়েছে):
template <typename VALUE>
struct derived_interface_type {
typedef typename interface<derived, VALUE> type;
};
typedef typename derived_interface_type<int>::type derived_t;
দুর্ভাগ্যক্রমে, উত্পন্ন টেমপ্লেটের প্রতিটি ইনস্ট্যান্টিশনের জন্য আপনার একটি derised_interface_type দরকার, যদি না আমি এখনও শিখি না এমন অন্য কৌশল unless
derived
এটির টেমপ্লেট যুক্তি ছাড়াই ব্যবহার করা যেতে পারে, অর্থাৎ লাইনটিtypedef typename interface<derived, VALUE> type;
template <typename>
। এক অর্থে আপনি টেমপ্লেট প্যারামিটারগুলিকে একটি 'মেটাটাইপ' বলে মনে করতে পারেন; টেমপ্লেট প্যারামিটারের সাধারণ মেটাটাইপটি typename
যার অর্থ এটি একটি নিয়মিত ধরণের দ্বারা পূরণ করা প্রয়োজন; template
metatype মানে দরকার একটি টেমপ্লেট একটি রেফারেন্স দিয়ে পূর্ণ করা হবে। derived
এমন একটি টেমপ্লেট সংজ্ঞায়িত করে যা একটি typename
মেটাটাইপড প্যারামিটার গ্রহণ করে , সুতরাং এটি বিলটি ফিট করে এবং এখানে উল্লেখ করা যেতে পারে। ধারণা তৈরী কর?
typedef
। এছাড়াও, আপনি ডাইরাইভেড প্রকারের int
মতো একটি স্ট্যান্ডার্ড কনস্ট্রাক্ট ব্যবহার করে আপনার প্রথম উদাহরণে সদৃশটিকে এড়াতে পারবেন value_type
।
typedef
ব্লক ২ থেকে সমস্যাটি পেতে পারি বলে কেবল সি ++ ১১ উল্লেখ করেছি তবে পয়েন্ট 2 বৈধ বলে আমি মনে করি ... হ্যাঁ, সম্ভবত এটি একই কাজ করার সহজ উপায় হবে।
এটিই আমি ছুটে এসেছি:
template<class A>
class B
{
A& a;
};
template<class B>
class A
{
B b;
};
class AInstance : A<B<A<B<A<B<A<B<... (oh oh)>>>>>>>>
{
};
এতে সমাধান করা যেতে পারে:
template<class A>
class B
{
A& a;
};
template< template<class> class B>
class A
{
B<A> b;
};
class AInstance : A<B> //happy
{
};
বা (কার্য কোড):
template<class A>
class B
{
public:
A* a;
int GetInt() { return a->dummy; }
};
template< template<class> class B>
class A
{
public:
A() : dummy(3) { b.a = this; }
B<A> b;
int dummy;
};
class AInstance : public A<B> //happy
{
public:
void Print() { std::cout << b.GetInt(); }
};
int main()
{
std::cout << "hello";
AInstance test;
test.Print();
}
পিফালকন দ্বারা সরবরাহিত ভ্যারিয়্যাডিক টেম্পলেটগুলির সমাধানে, আমি ভেরিয়েডিক বিশেষায়নের লোভী প্রকৃতির কারণে আসলে স্ট্যান্ড :: ম্যাপের জন্য অস্ট্রিমে অপারেটরকে বিশেষায়িত করতে অসুবিধা পেয়েছি। এখানে একটি সামান্য সংশোধন যা আমার পক্ষে কাজ করেছে:
#include <iostream>
#include <vector>
#include <deque>
#include <list>
#include <map>
namespace containerdisplay
{
template<typename T, template<class,class...> class C, class... Args>
std::ostream& operator <<(std::ostream& os, const C<T,Args...>& objs)
{
std::cout << __PRETTY_FUNCTION__ << '\n';
for (auto const& obj : objs)
os << obj << ' ';
return os;
}
}
template< typename K, typename V>
std::ostream& operator << ( std::ostream& os,
const std::map< K, V > & objs )
{
std::cout << __PRETTY_FUNCTION__ << '\n';
for( auto& obj : objs )
{
os << obj.first << ": " << obj.second << std::endl;
}
return os;
}
int main()
{
{
using namespace containerdisplay;
std::vector<float> vf { 1.1, 2.2, 3.3, 4.4 };
std::cout << vf << '\n';
std::list<char> lc { 'a', 'b', 'c', 'd' };
std::cout << lc << '\n';
std::deque<int> di { 1, 2, 3, 4 };
std::cout << di << '\n';
}
std::map< std::string, std::string > m1
{
{ "foo", "bar" },
{ "baz", "boo" }
};
std::cout << m1 << std::endl;
return 0;
}
আমি সবেমাত্র ব্যবহার করেছি এমন কিছু থেকে এখানে একটি সাধারণীকৃত। এটি একটি খুব সাধারণ উদাহরণ হিসাবে আমি এটি পোস্ট করছি এবং এটি ডিফল্ট যুক্তিগুলির সাথে ব্যবহারিক ব্যবহারের ক্ষেত্রেও প্রদর্শন করে:
#include <vector>
template <class T> class Alloc final { /*...*/ };
template <template <class T> class allocator=Alloc> class MyClass final {
public:
std::vector<short,allocator<short>> field0;
std::vector<float,allocator<float>> field1;
};
এটি আপনার কোডের পঠনযোগ্যতা উন্নত করে, অতিরিক্ত ধরণের সুরক্ষা সরবরাহ করে এবং কিছু সংকলক প্রচেষ্টা সংরক্ষণ করে।
বলুন আপনি কোনও ধারকটির প্রতিটি উপাদান মুদ্রণ করতে চান, আপনি টেমপ্লেট টেম্পলেট প্যারামিটার ছাড়াই নিম্নলিখিত কোডটি ব্যবহার করতে পারেন
template <typename T> void print_container(const T& c)
{
for (const auto& v : c)
{
std::cout << v << ' ';
}
std::cout << '\n';
}
অথবা টেমপ্লেট টেম্পলেট প্যারামিটার সহ
template< template<typename, typename> class ContainerType, typename ValueType, typename AllocType>
void print_container(const ContainerType<ValueType, AllocType>& c)
{
for (const auto& v : c)
{
std::cout << v << ' ';
}
std::cout << '\n';
}
আপনি একটি পূর্ণসংখ্যা বলে মধ্যে পাস ধরে print_container(3)
। পূর্ববর্তী ক্ষেত্রে, টেমপ্লেট সংকলক দ্বারা ইনস্ট্যান্ট করা হবে যা c
লুপের জন্য লুপের ব্যবহার সম্পর্কে অভিযোগ করবে , পরবর্তী কোনও টেম্পলেটটি খুঁজে পাওয়া যায় না বলে টেমপ্লেটটি একেবারে ইনস্ট্যান্ট করবে না।
সাধারণভাবে বলতে গেলে, যদি আপনার টেম্পলেট শ্রেণি / ফাংশনটি টেম্পলেট শ্রেণিটিকে টেমপ্লেট প্যারামিটার হিসাবে পরিচালনা করতে ডিজাইন করা থাকে তবে এটি পরিষ্কার করা আরও ভাল।
আমি এটি সংস্করণযুক্ত ধরণের জন্য ব্যবহার করি।
আপনার যদি কোনও টেমপ্লেটের মাধ্যমে সংস্করণযুক্ত যেমন থাকে তবে MyType<version>
আপনি এমন একটি ফাংশন লিখতে পারেন যাতে আপনি সংস্করণ নম্বরটি ক্যাপচার করতে পারেন:
template<template<uint8_t> T, uint8_t Version>
Foo(const T<Version>& obj)
{
assert(Version > 2 && "Versions older than 2 are no longer handled");
...
switch (Version)
{
...
}
}
সুতরাং আপনি প্রতিটি ধরণের ওভারলোডের পরিবর্তে প্রকারটি যে ধরণের পাস হচ্ছে তা নির্ভর করে বিভিন্ন জিনিস করতে পারেন। আপনার জেনেরিক উপায়ে রূপান্তর ফাংশন থাকতে পারে MyType<Version>
এবং ফিরে আসতে MyType<Version+1>
পারে এবং এমনকি ToNewest()
কোনও পুরানো সংস্করণ থেকে কোনও ধরণের সর্বশেষতম সংস্করণ ফেরত দেয় এমন ফাংশনটি পুনরুদ্ধার করতে পারে (লগগুলির জন্য খুব দরকারী যে কিছুক্ষণ আগে সঞ্চিত থাকতে পারে) তবে আজকের নবীনতম সরঞ্জাম দিয়ে প্রক্রিয়া করা প্রয়োজন)।