मैंने नीति-आधारित वर्ग डिजाइन करने के लिए टेम्पलेट टेम्पलेट मापदंडों (जो टेम्पलेट के मापदंडों के रूप में टेम्पलेट लेते हैं) का उपयोग करके C ++ के कुछ उदाहरण देखे हैं। इस तकनीक के और क्या उपयोग हैं?
मैंने नीति-आधारित वर्ग डिजाइन करने के लिए टेम्पलेट टेम्पलेट मापदंडों (जो टेम्पलेट के मापदंडों के रूप में टेम्पलेट लेते हैं) का उपयोग करके C ++ के कुछ उदाहरण देखे हैं। इस तकनीक के और क्या उपयोग हैं?
जवाबों:
मुझे लगता है कि आपको एक पैरामीटर पास करने के लिए टेम्पलेट टेम्प्लेट सिंटैक्स का उपयोग करने की आवश्यकता है जिसका प्रकार इस तरह से दूसरे टेम्पलेट पर निर्भर टेम्पलेट है:
template <template<class> class H, class S>
void f(const H<S> &value) {
}
यहां, H
एक टेम्प्लेट है, लेकिन मैं चाहता था कि यह फ़ंक्शन सभी विशिष्टताओं से निपटे H
।
नोट : मैं कई वर्षों से c ++ प्रोग्रामिंग कर रहा हूं और केवल एक बार इसकी आवश्यकता है। मुझे लगता है कि यह शायद ही कभी आवश्यक विशेषता है (जब आपको इसकी आवश्यकता है!)।
मैं अच्छे उदाहरणों के बारे में सोचने की कोशिश कर रहा हूं, और ईमानदार होने के लिए, अधिकांश समय यह आवश्यक नहीं है, लेकिन आइए एक उदाहरण पर चर्चा करें। चलो बहाना है कि एक 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!
अद्यतन : उदाहरण के लिए, जबकि यह काल्पनिक उदाहरण, c ++ 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>
वास्तव में, टेम्पलेट टेम्पलेट मापदंडों के लिए usecase बल्कि स्पष्ट है। एक बार जब आप सीख लेते हैं कि C ++ stdlib में मानक कंटेनर प्रकारों के लिए स्ट्रीम आउटपुट ऑपरेटरों को परिभाषित नहीं करने का छेद है, तो आप कुछ इस तरह से लिखना चाहेंगे:
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;
}
फिर आप यह पता लगाएंगे कि वेक्टर के लिए कोड सिर्फ एक ही है, फॉरवर्ड_लिस्ट एक ही है, वास्तव में, यहां तक कि मानचित्र प्रकारों की भीड़ के लिए यह अभी भी केवल एक ही है। उन टेम्प्लेट क्लासेस में मेटा-इंटरफ़ेस / प्रोटोकॉल को छोड़कर कुछ भी सामान्य नहीं है, और टेम्प्लेट टेम्पलेट पैरामीटर का उपयोग करके उन सभी में समानता को पकड़ने की अनुमति मिलती है। हालांकि एक टेम्प्लेट लिखने के लिए आगे बढ़ने से पहले, यह याद रखने के लिए एक संदर्भ की जांच करने के लायक है कि अनुक्रम कंटेनर 2 टेम्पलेट तर्क स्वीकार करते हैं - मूल्य प्रकार और आवंटन के लिए। जब आवंटनकर्ता चूक कर रहा है, तब भी हमें अपने टेम्पलेट ऑपरेटर में इसके अस्तित्व के लिए खाता होना चाहिए <<:
template<template <typename, typename> class Container, class V, class A>
std::ostream& operator<<(std::ostream& out, Container<V, A> const& v)
...
वोइला, जो मानक प्रोटोकॉल का पालन करने वाले सभी वर्तमान और भविष्य के अनुक्रम कंटेनरों के लिए स्वचालित रूप से काम करेगा। मिश्रण में मानचित्र जोड़ने के लिए, यह ध्यान में रखना चाहिए कि वे 4 टेम्प्लेट पैराम को स्वीकार करते हैं, इसलिए हमें ऑपरेटर के एक और संस्करण की आवश्यकता होगी << 4-एर्ग टेम्पलेट टेम्प्लेट परम के साथ। हम यह भी देखेंगे कि एसटीडी: जोड़ी को 2-arg ऑपरेटर के साथ प्रदान करने की कोशिश की जाती है << अनुक्रम प्रकारों के लिए जिसे हमने पहले परिभाषित किया था, इसलिए हम सिर्फ std :: pair के लिए एक विशेषज्ञता प्रदान करेंगे।
Btw, C + 11 के साथ जो वैरेडिक टेम्प्लेट की अनुमति देता है (और इस तरह से वैरेडिक टेम्प्लेट टेम्पलेट को अनुमति देता है), उन सभी पर शासन करने के लिए एकल ऑपरेटर << होना संभव होगा। उदाहरण के लिए:
#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>
{
...
};
वे बताते हैं: आमतौर पर, मेजबान वर्ग पहले से ही जानता है, या आसानी से कटौती कर सकता है, नीति वर्ग का टेम्पलेट तर्क। ऊपर के उदाहरण में, WidgetManager हमेशा Widget की वस्तुओं का प्रबंधन करता है, इसलिए उपयोगकर्ता को CreatPolicy की तात्कालिकता में फिर से विजेट को निर्दिष्ट करने की आवश्यकता होती है, अनावश्यक और संभावित रूप से खतरनाक है। इस मामले में, लाइब्रेरी कोड नीतियों को निर्दिष्ट करने के लिए टेम्पलेट टेम्पलेट मापदंडों का उपयोग कर सकता है।
इसका प्रभाव यह है कि क्लाइंट कोड 'विजेटस्ट्रीमर' का अधिक सुरुचिपूर्ण तरीके से उपयोग कर सकता है:
typedef WidgetManager<MyCreationPolicy> MyWidgetMgr;
इसके बजाय अधिक बोझिल, और त्रुटि प्रवण तरीका है कि एक परिभाषा टेम्पलेट टेम्पलेट तर्क की कमी की आवश्यकता होगी:
typedef WidgetManager< MyCreationPolicy<Widget> > MyWidgetMgr;
यहाँ मेरा CUDA संवेदी तंत्रिका नेटवर्क लाइब्रेरी से एक और व्यावहारिक उदाहरण है । मेरे पास निम्न वर्ग टेम्पलेट है:
template <class T> class Tensor
जो वास्तव में एन-आयामी मेट्रिसेस हेरफेर को लागू करता है। चाइल्ड क्लास टेम्प्लेट भी है:
template <class T> class TensorGPU : public Tensor<T>
जो एक ही कार्यक्षमता को लागू करता है, लेकिन GPU में। दोनों टेम्पलेट सभी बुनियादी प्रकारों के साथ काम कर सकते हैं, जैसे फ्लोट, डबल, इंट, आदि और मेरे पास एक क्लास टेम्पलेट (सरलीकृत) भी है:
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>
जिसमें वेट और टाइप के दोनों तरह के फ्लोट्स होंगे और GPU पर, लेकिन कनेक्शन_मैटिक्स हमेशा int होगा, या तो CPU पर (TT = Tensor को निर्दिष्ट करके) या GPU पर (TT = TensorGPU को निर्दिष्ट करके)।
कहते हैं कि आप बाल टेम्पलेट्स के एक सेट के लिए "इंटरफ़ेस" प्रदान करने के लिए CRTP का उपयोग कर रहे हैं; और माता-पिता और बच्चा दोनों अन्य टेम्पलेट तर्क में पैरामीट्रिक हैं:
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' के दोहराव पर ध्यान दें, जो वास्तव में एक ही प्रकार का पैरामीटर दोनों टेम्पलेट्स के लिए निर्दिष्ट है। इस दोहराव से बचने के लिए आप DERIVED के लिए एक टेम्प्लेट टेम्पलेट का उपयोग कर सकते हैं:
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;
ध्यान दें कि आप व्युत्पन्न टेम्पलेट को अन्य टेम्पलेट पैरामीटर (ओं) को सीधे प्रदान कर रहे हैं ; "इंटरफ़ेस" अभी भी उन्हें प्राप्त करता है।
यह आपको "इंटरफ़ेस" में टाइपपैड बनाने की सुविधा भी देता है जो कि प्रकार के मापदंडों पर निर्भर करता है, जो व्युत्पन्न टेम्पलेट से सुलभ होगा।
उपर्युक्त टाइपफाइफ़ काम नहीं करता है क्योंकि आप एक अनिर्दिष्ट टेम्पलेट में टाइप नहीं कर सकते हैं। हालाँकि, यह काम करता है (और C ++ 11 में टेम्पलेट टाइपडिफ के लिए मूल समर्थन है):
template <typename VALUE>
struct derived_interface_type {
typedef typename interface<derived, VALUE> type;
};
typedef typename derived_interface_type<int>::type derived_t;
दुर्भाग्यवश, व्युत्पन्न टेम्पलेट के प्रत्येक तात्कालिकता के लिए आपको एक व्युत्पन्न_हृदय_प्रकार की आवश्यकता है, जब तक कि एक और चाल नहीं है जो मैंने अभी तक नहीं सीखा है।
derived
का उपयोग इसके टेम्प्लेट के तर्कों के बिना कैसे किया जा सकता है, अर्थात लाइनtypedef typename interface<derived, VALUE> type;
template <typename>
। एक मायने में आप '' मेटाटाइप '' के रूप में टेम्प्लेट मापदंडों के बारे में सोच सकते हैं; एक टेम्पलेट पैरामीटर के लिए सामान्य मेटाटाइप है, typename
जिसका अर्थ है कि इसे एक नियमित प्रकार से भरना होगा; template
metatype साधन इसकी आवश्यकता है एक टेम्पलेट के लिए एक संदर्भ के साथ भरे जाने हैं। derived
एक टेम्पलेट को परिभाषित करता है जो एक typename
मेटाटाइप्ड पैरामीटर को स्वीकार करता है , इसलिए यह बिल को फिट करता है और यहां संदर्भित किया जा सकता है। सही बात?
typedef
। इसके अलावा, आप DERIVED प्रकार में int
एक मानक निर्माण का उपयोग करके अपने पहले उदाहरण में डुप्लिकेट से बच सकते हैं value_type
।
typedef
ब्लॉक 2 से समस्या के आसपास पहुंच सकते हैं । लेकिन बिंदु 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();
}
Pfalcon द्वारा प्रदान किए जाने वाले वैरेडिक टेम्प्लेट के साथ समाधान में, मुझे वास्तव में stad :: मानचित्र के लिए ओस्ट्रीम ऑपरेटर को समझना मुश्किल हो गया, जो कि वैरेडिक विशेषज्ञता के लालची स्वभाव के कारण है। यहाँ एक छोटा सा संशोधन है जो मेरे लिए काम कर रहा है:
#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()
फ़ंक्शन के लिए फिर से तैयार करते हैं जो किसी भी पुराने संस्करण से एक प्रकार का नवीनतम संस्करण लौटाता है (लॉग के लिए बहुत उपयोगी है जो थोड़ी देर पहले संग्रहीत किया गया हो सकता है) लेकिन आज के नवीनतम उपकरण के साथ संसाधित होने की आवश्यकता है)।