C++
ऑपरेटर ओवरलोडिंग
खोज…
परिचय
सी ++ में, उपयोगकर्ता-परिभाषित प्रकारों के लिए +
और ->
जैसे ऑपरेटरों को परिभाषित करना संभव है। उदाहरण के लिए, <string>
हैडर एक को परिभाषित करता है +
CONCATENATE तार करने के लिए ऑपरेटर। यह operator
कीवर्ड का उपयोग करके एक ऑपरेटर फ़ंक्शन को परिभाषित करके किया जाता है।
टिप्पणियों
बिल्ट-इन प्रकारों के ऑपरेटरों को बदला नहीं जा सकता है, ऑपरेटर केवल उपयोगकर्ता-परिभाषित प्रकारों के लिए अतिभारित हो सकते हैं। यही है, कम से कम ऑपरेंड्स में से एक उपयोगकर्ता-परिभाषित प्रकार का होना चाहिए।
निम्नलिखित ऑपरेटरों को अतिभारित नहीं किया जा सकता है:
- सदस्य पहुंच या "डॉट" ऑपरेटर
.
- सदस्य अभिगम ऑपरेटर के लिए सूचक
.*
- गुंजाइश रिज़ॉल्यूशन ऑपरेटर,
::
- टर्नरी सशर्त ऑपरेटर ;
?:
-
dynamic_cast
,static_cast
,reinterpret_cast
,const_cast
,typeid
,sizeof
,alignof
, औरnoexcept
- प्रीप्रोसेसिंग निर्देश,
#
और##
, जो किसी भी प्रकार की जानकारी उपलब्ध होने से पहले निष्पादित किए जाते हैं।
कुछ ऑपरेटर हैं जिन्हें आपको (99.98% समय) ओवरलोड नहीं करना चाहिए:
-
&&
और||
(पसंद करें, इसके बजाय,bool
निहित रूपांतरण का उपयोग करें) -
,
- पता-के ऑपरेटर (एकल
&
)
क्यों? क्योंकि वे ऑपरेटरों को ओवरलोड करते हैं जो दूसरे प्रोग्रामर से कभी उम्मीद नहीं कर सकते हैं, जिसके परिणामस्वरूप प्रत्याशित की तुलना में अलग व्यवहार होता है।
उदाहरण के लिए, उपयोगकर्ता परिभाषित &&
और ||
इन ऑपरेटरों के अधिभार अपने शॉर्ट-सर्किट मूल्यांकन को खो देते हैं और अपने विशेष अनुक्रमण गुणों (सी ++ 17) को खो देते हैं , अनुक्रमण मुद्दा भी लागू होता है ,
ऑपरेटर ओवरलोड करता है।
अंकगणितीय आपरेटर
आप सभी बुनियादी अंकगणितीय ऑपरेटरों को अधिभारित कर सकते हैं:
-
+
और+=
-
-
और-=
-
*
और*=
-
/
और/=
-
&
&=
-
|
और|=
-
^
और^=
-
>>
और>>=
-
<<
और<<=
सभी ऑपरेटरों के लिए ओवरलोडिंग समान है। स्पष्टीकरण के लिए नीचे स्क्रॉल करें
class
/ struct
बाहर ओवरलोडिंग:
//operator+ should be implemented in terms of operator+=
T operator+(T lhs, const T& rhs)
{
lhs += rhs;
return lhs;
}
T& operator+=(T& lhs, const T& rhs)
{
//Perform addition
return lhs;
}
class
/ struct
अंदर ओवरलोडिंग:
//operator+ should be implemented in terms of operator+=
T operator+(const T& rhs)
{
*this += rhs;
return *this;
}
T& operator+=(const T& rhs)
{
//Perform addition
return *this;
}
नोट: operator+
को नॉन-कास्ट मान द्वारा वापस लौटना चाहिए, क्योंकि एक संदर्भ देने से कोई मतलब नहीं होगा (यह एक नई वस्तु लौटाता है) और न ही एक const
वैल्यू लौटाएगा (आपको आम तौर पर const
द्वारा वापस नहीं लौटना चाहिए)। पहला तर्क मूल्य द्वारा पारित किया जाता है, क्यों? इसलिये
- आप मूल वस्तु को संशोधित नहीं कर सकते हैं (
Object foobar = foo + bar;
foo
को संशोधित नहीं करना चाहिए, इससे कोई मतलब नहीं होगा) - आप इसे
const
नहीं कर सकते, क्योंकि आपको ऑब्जेक्ट को संशोधित करने में सक्षम होना होगा (क्योंकिoperator+
संदर्भ में लागू होता हैoperator+=
, जो ऑब्जेक्ट को संशोधित करता है)
const&
पासिंग एक विकल्प होगा, लेकिन फिर आपको पास की गई वस्तु की एक अस्थायी प्रतिलिपि बनानी होगी। मूल्य से गुजरकर, कंपाइलर आपके लिए करता है।
operator+=
स्वयं के लिए एक संदर्भ देता है, क्योंकि तब उन्हें श्रृंखलाबद्ध करना संभव है (उसी चर का उपयोग न करें, हालांकि अनुक्रम बिंदुओं के कारण यह अपरिभाषित व्यवहार होगा)।
पहला तर्क एक संदर्भ है (हम इसे संशोधित करना चाहते हैं), लेकिन const
नहीं, क्योंकि तब आप इसे संशोधित नहीं कर पाएंगे। दूसरे तर्क को संशोधित नहीं किया जाना चाहिए, और इसलिए प्रदर्शन कारण const&
द्वारा पारित किया जाता है const&
(कॉन्स्ट सन्दर्भ से गुजरना मूल्य से अधिक तेज़ होता है)।
गैर-संचालक
आप 2 यूनिरी ऑपरेटर्स को ओवरलोड कर सकते हैं:
-
++foo
औरfoo++
-
--foo
औरfoo--
ओवरलोडिंग दोनों प्रकारों ( ++
और --
) के लिए समान है। स्पष्टीकरण के लिए नीचे स्क्रॉल करें
class
/ struct
बाहर ओवरलोडिंग:
//Prefix operator ++foo
T& operator++(T& lhs)
{
//Perform addition
return lhs;
}
//Postfix operator foo++ (int argument is used to separate pre- and postfix)
//Should be implemented in terms of ++foo (prefix operator)
T operator++(T& lhs, int)
{
T t(lhs);
++lhs;
return t;
}
class
/ struct
अंदर ओवरलोडिंग:
//Prefix operator ++foo
T& operator++()
{
//Perform addition
return *this;
}
//Postfix operator foo++ (int argument is used to separate pre- and postfix)
//Should be implemented in terms of ++foo (prefix operator)
T operator++(int)
{
T t(*this);
++(*this);
return t;
}
नोट: उपसर्ग ऑपरेटर अपने आप में एक संदर्भ देता है, ताकि आप उस पर परिचालन जारी रख सकें। पहला तर्क एक संदर्भ है, क्योंकि उपसर्ग ऑपरेटर ऑब्जेक्ट को बदलता है, यही कारण है कि यह const
नहीं है (आप इसे अन्यथा संशोधित नहीं कर पाएंगे)।
पोस्टफ़िक्स ऑपरेटर अस्थायी (पिछला मान) मान से लौटता है, और इसलिए यह एक संदर्भ नहीं हो सकता है, क्योंकि यह एक अस्थायी के लिए एक संदर्भ होगा, जो फ़ंक्शन के अंत में कचरा मूल्य होगा, क्योंकि अस्थायी चर निकल जाता है की गुंजाइश)। यह भी const
नहीं हो सकता, क्योंकि आपको इसे सीधे संशोधित करने में सक्षम होना चाहिए।
पहला तर्क "कॉलिंग" ऑब्जेक्ट के लिए एक नॉन- const
संदर्भ है, क्योंकि यदि यह const
, तो आप इसे संशोधित नहीं कर पाएंगे, और यदि यह एक संदर्भ नहीं थे, तो आप मूल मान को नहीं बदलेंगे।
यह उपसर्ग ऑपरेटर अधिभार में आवश्यक नकल की वजह से है कि इसे छोरों के for
उपसर्ग ++ के बजाय उपसर्ग ++ का उपयोग करने की आदत बनाना बेहतर है। से for
पाश परिप्रेक्ष्य, वे आमतौर पर कार्यात्मक रूप से बराबर कर रहे हैं, लेकिन वहाँ विशेष रूप से सदस्यों को कॉपी करने के का एक बहुत के साथ "वसा" वर्गों के साथ, उपसर्ग ++ का उपयोग करने के लिए एक मामूली प्रदर्शन लाभ हो सकता है। लूप के लिए उपसर्ग ++ का उपयोग करने का उदाहरण:
for (list<string>::const_iterator it = tokens.begin();
it != tokens.end();
++it) { // Don't use it++
...
}
तुलनात्मक संचालक
आप सभी तुलना ऑपरेटरों को अधिभारित कर सकते हैं:
-
==
और!=
-
>
और<
-
>=
और<=
उन सभी ऑपरेटरों को ओवरलोड करने का अनुशंसित तरीका केवल 2 ऑपरेटरों ( ==
और <
) को लागू करने और फिर बाकी को परिभाषित करने के लिए उपयोग करना है। स्पष्टीकरण के लिए नीचे स्क्रॉल करें
class
/ struct
बाहर ओवरलोडिंग:
//Only implement those 2
bool operator==(const T& lhs, const T& rhs) { /* Compare */ }
bool operator<(const T& lhs, const T& rhs) { /* Compare */ }
//Now you can define the rest
bool operator!=(const T& lhs, const T& rhs) { return !(lhs == rhs); }
bool operator>(const T& lhs, const T& rhs) { return rhs < lhs; }
bool operator<=(const T& lhs, const T& rhs) { return !(lhs > rhs); }
bool operator>=(const T& lhs, const T& rhs) { return !(lhs < rhs); }
class
/ struct
अंदर ओवरलोडिंग:
//Note that the functions are const, because if they are not const, you wouldn't be able
//to call them if the object is const
//Only implement those 2
bool operator==(const T& rhs) const { /* Compare */ }
bool operator<(const T& rhs) const { /* Compare */ }
//Now you can define the rest
bool operator!=(const T& rhs) const { return !(*this == rhs); }
bool operator>(const T& rhs) const { return rhs < *this; }
bool operator<=(const T& rhs) const { return !(*this > rhs); }
bool operator>=(const T& rhs) const { return !(*this < rhs); }
ऑपरेटर स्पष्ट रूप से एक bool
वापस करते हैं, जो संबंधित ऑपरेशन के लिए true
या false
संकेत देता true
।
सभी संचालक अपने तर्क const&
द्वारा लेते हैं, क्योंकि केवल एक ही चीज़ जो ऑपरेटर करते हैं, तुलना की जाती है, इसलिए उन्हें वस्तुओं को संशोधित नहीं करना चाहिए। द्वारा पासिंग &
(संदर्भ) तेजी से मूल्य की तुलना में है, और यह सुनिश्चित करें कि ऑपरेटरों इसे संशोधित नहीं करना है, यह एक है const
-reference।
ध्यान दें कि class
/ struct
अंदर के संचालकों को const
रूप में परिभाषित किया गया है, इसका कारण यह है कि फ़ंक्शंस const
होने के बिना, const
ऑब्जेक्ट्स की तुलना करना संभव नहीं होगा, क्योंकि कंपाइलर को पता नहीं है कि ऑपरेटर कुछ भी संशोधित नहीं करते हैं।
रूपांतरण ऑपरेटर
आप टाइप ऑपरेटरों को अधिभारित कर सकते हैं, ताकि आपके प्रकार को स्पष्ट रूप से निर्दिष्ट प्रकार में परिवर्तित किया जा सके।
रूपांतरण ऑपरेटर को एक class
/ struct
में परिभाषित किया जाना चाहिए:
operator T() const { /* return something */ }
ध्यान दें: ऑपरेटर है const
अनुमति देने के लिए const
वस्तुओं परिवर्तित किया।
उदाहरण:
struct Text
{
std::string text;
// Now Text can be implicitly converted into a const char*
/*explicit*/ operator const char*() const { return text.data(); }
// ^^^^^^^
// to disable implicit conversion
};
Text t;
t.text = "Hello world!";
//Ok
const char* copyoftext = t;
सबस्क्रिप्ट ऑपरेटर
आप सरणी सबस्क्रिप्ट ऑपरेटर []
को भी अधिभारित कर सकते हैं।
आपको हमेशा (99.98% समय) 2 संस्करणों, एक const
और नॉट- const
संस्करण को लागू करना चाहिए, क्योंकि यदि ऑब्जेक्ट const
, तो यह []
द्वारा लौटाए गए ऑब्जेक्ट को संशोधित करने में सक्षम नहीं होना चाहिए।
तर्कों को मूल्य के बजाय const&
द्वारा पारित किया जाता है क्योंकि संदर्भ से गुजरना मूल्य से अधिक तेज है, और const
इसलिए कि ऑपरेटर गलती से सूचकांक को नहीं बदलता है।
ऑपरेटर संदर्भ द्वारा लौटते हैं, क्योंकि डिज़ाइन द्वारा आप वस्तु को संशोधित कर सकते हैं []
वापसी, अर्थात:
std::vector<int> v{ 1 };
v[0] = 2; //Changes value of 1 to 2
//wouldn't be possible if not returned by reference
आप केवल एक class
/ struct
अंदर ही ओवरलोड कर सकते हैं:
//I is the index type, normally an int
T& operator[](const I& index)
{
//Do something
//return something
}
//I is the index type, normally an int
const T& operator[](const I& index) const
{
//Do something
//return something
}
एकाधिक सबस्क्रिप्ट ऑपरेटर, [][]...
, प्रॉक्सी ऑब्जेक्ट के माध्यम से प्राप्त किया जा सकता है। एक सरल पंक्ति-प्रमुख मैट्रिक्स वर्ग का निम्न उदाहरण यह प्रदर्शित करता है:
template<class T>
class matrix {
// class enabling [][] overload to access matrix elements
template <class C>
class proxy_row_vector {
using reference = decltype(std::declval<C>()[0]);
using const_reference = decltype(std::declval<C const>()[0]);
public:
proxy_row_vector(C& _vec, std::size_t _r_ind, std::size_t _cols)
: vec(_vec), row_index(_r_ind), cols(_cols) {}
const_reference operator[](std::size_t _col_index) const {
return vec[row_index*cols + _col_index];
}
reference operator[](std::size_t _col_index) {
return vec[row_index*cols + _col_index];
}
private:
C& vec;
std::size_t row_index; // row index to access
std::size_t cols; // number of columns in matrix
};
using const_proxy = proxy_row_vector<const std::vector<T>>;
using proxy = proxy_row_vector<std::vector<T>>;
public:
matrix() : mtx(), rows(0), cols(0) {}
matrix(std::size_t _rows, std::size_t _cols)
: mtx(_rows*_cols), rows(_rows), cols(_cols) {}
// call operator[] followed by another [] call to access matrix elements
const_proxy operator[](std::size_t _row_index) const {
return const_proxy(mtx, _row_index, cols);
}
proxy operator[](std::size_t _row_index) {
return proxy(mtx, _row_index, cols);
}
private:
std::vector<T> mtx;
std::size_t rows;
std::size_t cols;
};
फंक्शन कॉल ऑपरेटर
आप फ़ंक्शन कॉल ऑपरेटर को ओवरलोड कर सकते हैं ()
:
ओवरलोडिंग एक class
/ struct
अंदर की जानी चाहिए:
//R -> Return type
//Types -> any different type
R operator()(Type name, Type2 name2, ...)
{
//Do something
//return something
}
//Use it like this (R is return type, a and b are variables)
R foo = object(a, b, ...);
उदाहरण के लिए:
struct Sum
{
int operator()(int a, int b)
{
return a + b;
}
};
//Create instance of struct
Sum sum;
int result = sum(1, 1); //result == 2
असाइनमेंट ऑपरेटर
असाइनमेंट ऑपरेटर सबसे महत्वपूर्ण ऑपरेटरों में से एक है क्योंकि यह आपको एक चर की स्थिति को बदलने की अनुमति देता है।
यदि आप अपने class
/ struct
लिए एसिगमेंट ऑपरेटर को अधिभार नहीं देते हैं, तो यह संकलक द्वारा स्वचालित रूप से उत्पन्न होता है: स्वचालित रूप से उत्पन्न असाइनमेंट ऑपरेटर एक "सदस्यवार असाइनमेंट" करता है, अर्थात सभी सदस्यों पर असाइनमेंट ऑपरेटरों को आमंत्रित करके, इसलिए एक ऑब्जेक्ट कॉपी किया जाता है; दूसरे, समय पर एक सदस्य। असाइनमेंट ऑपरेटर को तब ओवरलोड किया जाना चाहिए जब साधारण सदस्यवार असाइनमेंट आपकी class
/ struct
के लिए उपयुक्त न हो, उदाहरण के लिए यदि आपको किसी ऑब्जेक्ट की गहरी कॉपी करने की आवश्यकता है।
असाइनमेंट ऑपरेटर को ओवरलोड करना =
आसान है, लेकिन आपको कुछ सरल चरणों का पालन करना चाहिए।
- स्व-असाइनमेंट के लिए टेस्ट। यह जाँच दो कारणों से महत्वपूर्ण है:
- एक स्व-असाइनमेंट एक अनावश्यक प्रतिलिपि है, इसलिए इसे प्रदर्शन करने का कोई मतलब नहीं है;
- अगला चरण स्व-असाइनमेंट के मामले में काम नहीं करेगा।
- पुराने डेटा को साफ करें। पुराने डेटा को नए से बदला जाना चाहिए। अब, आप पिछले चरण के दूसरे कारण को समझ सकते हैं: यदि ऑब्जेक्ट की सामग्री नष्ट हो गई थी, तो एक स्व-असाइनमेंट कॉपी प्रदर्शन करने में विफल हो जाएगा।
- सभी सदस्यों को कॉपी करें। यदि आप अपनी
class
या अपनीstruct
लिए एसिगमेंट ऑपरेटर को ओवरलोड करते हैं, तो यह संकलक द्वारा स्वचालित रूप से उत्पन्न नहीं होता है, इसलिए आपको सभी सदस्यों को अन्य ऑब्जेक्ट से कॉपी करने का प्रभार लेने की आवश्यकता होगी। - वापसी
*this
। ऑपरेटर संदर्भ द्वारा अपने आप वापस आ जाता है, क्योंकि यह चैनिंग (यानीint b = (a = 6) + 4; //b == 10
) की अनुमति देता है।
//T is some type
T& operator=(const T& other)
{
//Do something (like copying values)
return *this;
}
नोट: other
द्वारा पारित कर दिया है const&
, क्योंकि वस्तु निर्धारित किये जाने से बदला नहीं जाना चाहिए, और मूल्य की तुलना में संदर्भ द्वारा पारित करने के लिए तेजी से है, और की तुलना में सुनिश्चित करने के लिए operator=
यह गलती को संशोधित नहीं है, यह है const
।
असाइनमेंट ऑपरेटर को केवल class
/ struct
में ओवरलोड किया जा सकता है, क्योंकि =
का लेफ्ट वैल्यू हमेशा class
/ struct
ही होता है। एक नि: शुल्क फ़ंक्शन के रूप में इसे परिभाषित करने की यह गारंटी नहीं है, और इसकी वजह से यह अस्वीकृत है।
जब आप इसे class
/ struct
में घोषित करते हैं, तो लेफ्ट वैल्यू का मतलब class
/ struct
ही होता है, इसलिए इसमें कोई समस्या नहीं है।
संचालक नहीं है
बिटवाइज़ को ओवरलोड नहीं करना ( ~
) काफी सरल है। स्पष्टीकरण के लिए नीचे स्क्रॉल करें
class
/ struct
बाहर ओवरलोडिंग:
T operator~(T lhs)
{
//Do operation
return lhs;
}
class
/ struct
अंदर ओवरलोडिंग:
T operator~()
{
T t(*this);
//Do operation
return t;
}
नोट: operator~
मूल्य से वापस आ जाता है, क्योंकि उसे एक नया मान (संशोधित मूल्य) वापस करना पड़ता है, और मूल्य का संदर्भ नहीं होता है (यह अस्थायी वस्तु का संदर्भ होगा, जिसमें जैसे ही कचरा मूल्य होगा) ऑपरेटर किया जाता है)। या तो const
न करें क्योंकि कॉलिंग कोड बाद में इसे संशोधित करने में सक्षम होना चाहिए (यानी int a = ~a + 1;
संभव होना चाहिए)।
अंदर class
/ struct
, आप एक अस्थायी वस्तु बनाने के लिए है, क्योंकि आप संशोधित नहीं कर सकते this
, के रूप में यह मूल वस्तु है, जो मामला नहीं होना चाहिए संशोधित करेगा।
I / O के लिए बिट शिफ्ट ऑपरेटर
ऑपरेटरों <<
और >>
आमतौर पर "लिखने" और "पढ़ने" ऑपरेटरों के रूप में उपयोग किया जाता है:
-
std::ostream
overloads<<
अंतर्निहित स्ट्रीम में चर लिखने के लिए (उदाहरण:std::cout
) -
std::istream
overloads>>
अंतर्निहित स्ट्रीम से चर तक पढ़ने के लिए (उदाहरण:std::cin
)
जिस तरह से वे ऐसा करते हैं यदि आप उन्हें class
/ struct
बाहर "सामान्य रूप से" ओवरलोड करना चाहते हैं, सिवाय इसके कि तर्कों को निर्दिष्ट करना एक ही प्रकार का नहीं है:
- रिटर्न प्रकार वह स्ट्रीम है जिसे आप ओवरलोड करना चाहते हैं (उदाहरण के लिए,
std::ostream
) संदर्भ के आधार पर, चैनिंग की अनुमति देने के लिए (चैनिंग:std::cout << a << b;
)। उदाहरण:std::ostream&
-
lhs
रिटर्न प्रकार के समान होगा -
rhs
वह प्रकार है जिसे आप प्रदर्शन कारण के लिए मान के बजाय (यानीT
) से ओवरलोडिंग की अनुमति देना चाहते हैं, जिसेconst&
द्वारा पास किया गया है (rhs
को बदला नहीं जाना चाहिए)। उदाहरण:const Vector&
उदाहरण:
//Overload std::ostream operator<< to allow output from Vector's
std::ostream& operator<<(std::ostream& lhs, const Vector& rhs)
{
lhs << "x: " << rhs.x << " y: " << rhs.y << " z: " << rhs.z << '\n';
return lhs;
}
Vector v = { 1, 2, 3};
//Now you can do
std::cout << v;
जटिल संख्या पर दोबारा गौर किया गया
नीचे दिए गए कोड एक बहुत ही सरल जटिल संख्या प्रकार को लागू करते हैं, जिसके लिए भाषा का प्रकार पदोन्नति नियमों का पालन करते हुए अंतर्निहित फ़ील्ड को स्वचालित रूप से बढ़ावा दिया जाता है, एक अलग क्षेत्र के सदस्य के साथ चार बुनियादी ऑपरेटरों (+, -, *, और /) के आवेदन के तहत। (यह एक और complex<T>
या कुछ स्केलर प्रकार हो)।
इसका उद्देश्य एक समग्र उदाहरण होना है, जो टेम्प्लेट के बुनियादी उपयोग के साथ-साथ ओवरलोडिंग ऑपरेटर को कवर करता है।
#include <type_traits>
namespace not_std{
using std::decay_t;
//----------------------------------------------------------------
// complex< value_t >
//----------------------------------------------------------------
template<typename value_t>
struct complex
{
value_t x;
value_t y;
complex &operator += (const value_t &x)
{
this->x += x;
return *this;
}
complex &operator += (const complex &other)
{
this->x += other.x;
this->y += other.y;
return *this;
}
complex &operator -= (const value_t &x)
{
this->x -= x;
return *this;
}
complex &operator -= (const complex &other)
{
this->x -= other.x;
this->y -= other.y;
return *this;
}
complex &operator *= (const value_t &s)
{
this->x *= s;
this->y *= s;
return *this;
}
complex &operator *= (const complex &other)
{
(*this) = (*this) * other;
return *this;
}
complex &operator /= (const value_t &s)
{
this->x /= s;
this->y /= s;
return *this;
}
complex &operator /= (const complex &other)
{
(*this) = (*this) / other;
return *this;
}
complex(const value_t &x, const value_t &y)
: x{x}
, y{y}
{}
template<typename other_value_t>
explicit complex(const complex<other_value_t> &other)
: x{static_cast<const value_t &>(other.x)}
, y{static_cast<const value_t &>(other.y)}
{}
complex &operator = (const complex &) = default;
complex &operator = (complex &&) = default;
complex(const complex &) = default;
complex(complex &&) = default;
complex() = default;
};
// Absolute value squared
template<typename value_t>
value_t absqr(const complex<value_t> &z)
{ return z.x*z.x + z.y*z.y; }
//----------------------------------------------------------------
// operator - (negation)
//----------------------------------------------------------------
template<typename value_t>
complex<value_t> operator - (const complex<value_t> &z)
{ return {-z.x, -z.y}; }
//----------------------------------------------------------------
// operator +
//----------------------------------------------------------------
template<typename left_t,typename right_t>
auto operator + (const complex<left_t> &a, const complex<right_t> &b)
-> complex<decay_t<decltype(a.x + b.x)>>
{ return{a.x + b.x, a.y + b.y}; }
template<typename left_t,typename right_t>
auto operator + (const left_t &a, const complex<right_t> &b)
-> complex<decay_t<decltype(a + b.x)>>
{ return{a + b.x, b.y}; }
template<typename left_t,typename right_t>
auto operator + (const complex<left_t> &a, const right_t &b)
-> complex<decay_t<decltype(a.x + b)>>
{ return{a.x + b, a.y}; }
//----------------------------------------------------------------
// operator -
//----------------------------------------------------------------
template<typename left_t,typename right_t>
auto operator - (const complex<left_t> &a, const complex<right_t> &b)
-> complex<decay_t<decltype(a.x - b.x)>>
{ return{a.x - b.x, a.y - b.y}; }
template<typename left_t,typename right_t>
auto operator - (const left_t &a, const complex<right_t> &b)
-> complex<decay_t<decltype(a - b.x)>>
{ return{a - b.x, - b.y}; }
template<typename left_t,typename right_t>
auto operator - (const complex<left_t> &a, const right_t &b)
-> complex<decay_t<decltype(a.x - b)>>
{ return{a.x - b, a.y}; }
//----------------------------------------------------------------
// operator *
//----------------------------------------------------------------
template<typename left_t, typename right_t>
auto operator * (const complex<left_t> &a, const complex<right_t> &b)
-> complex<decay_t<decltype(a.x * b.x)>>
{
return {
a.x*b.x - a.y*b.y,
a.x*b.y + a.y*b.x
};
}
template<typename left_t, typename right_t>
auto operator * (const left_t &a, const complex<right_t> &b)
-> complex<decay_t<decltype(a * b.x)>>
{ return {a * b.x, a * b.y}; }
template<typename left_t, typename right_t>
auto operator * (const complex<left_t> &a, const right_t &b)
-> complex<decay_t<decltype(a.x * b)>>
{ return {a.x * b, a.y * b}; }
//----------------------------------------------------------------
// operator /
//----------------------------------------------------------------
template<typename left_t, typename right_t>
auto operator / (const complex<left_t> &a, const complex<right_t> &b)
-> complex<decay_t<decltype(a.x / b.x)>>
{
const auto r = absqr(b);
return {
( a.x*b.x + a.y*b.y) / r,
(-a.x*b.y + a.y*b.x) / r
};
}
template<typename left_t, typename right_t>
auto operator / (const left_t &a, const complex<right_t> &b)
-> complex<decay_t<decltype(a / b.x)>>
{
const auto s = a/absqr(b);
return {
b.x * s,
-b.y * s
};
}
template<typename left_t, typename right_t>
auto operator / (const complex<left_t> &a, const right_t &b)
-> complex<decay_t<decltype(a.x / b)>>
{ return {a.x / b, a.y / b}; }
}// namespace not_std
int main(int argc, char **argv)
{
using namespace not_std;
complex<float> fz{4.0f, 1.0f};
// makes a complex<double>
auto dz = fz * 1.0;
// still a complex<double>
auto idz = 1.0f/dz;
// also a complex<double>
auto one = dz * idz;
// a complex<double> again
auto one_again = fz * idz;
// Operator tests, just to make sure everything compiles.
complex<float> a{1.0f, -2.0f};
complex<double> b{3.0, -4.0};
// All of these are complex<double>
auto c0 = a + b;
auto c1 = a - b;
auto c2 = a * b;
auto c3 = a / b;
// All of these are complex<float>
auto d0 = a + 1;
auto d1 = 1 + a;
auto d2 = a - 1;
auto d3 = 1 - a;
auto d4 = a * 1;
auto d5 = 1 * a;
auto d6 = a / 1;
auto d7 = 1 / a;
// All of these are complex<double>
auto e0 = b + 1;
auto e1 = 1 + b;
auto e2 = b - 1;
auto e3 = 1 - b;
auto e4 = b * 1;
auto e5 = 1 * b;
auto e6 = b / 1;
auto e7 = 1 / b;
return 0;
}
नामांकित संचालक
आप नामांकित ऑपरेटरों के साथ C ++ का विस्तार कर सकते हैं जो मानक C ++ ऑपरेटरों द्वारा "उद्धृत" किए गए हैं।
पहले हम एक दर्जन से अधिक पुस्तकालय शुरू करते हैं:
namespace named_operator {
template<class D>struct make_operator{constexpr make_operator(){}};
template<class T, char, class O> struct half_apply { T&& lhs; };
template<class Lhs, class Op>
half_apply<Lhs, '*', Op> operator*( Lhs&& lhs, make_operator<Op> ) {
return {std::forward<Lhs>(lhs)};
}
template<class Lhs, class Op, class Rhs>
auto operator*( half_apply<Lhs, '*', Op>&& lhs, Rhs&& rhs )
-> decltype( named_invoke( std::forward<Lhs>(lhs.lhs), Op{}, std::forward<Rhs>(rhs) ) )
{
return named_invoke( std::forward<Lhs>(lhs.lhs), Op{}, std::forward<Rhs>(rhs) );
}
}
यह अभी तक कुछ नहीं करता है।
सबसे पहले, संलग्न वैक्टर
namespace my_ns {
struct append_t : named_operator::make_operator<append_t> {};
constexpr append_t append{};
template<class T, class A0, class A1>
std::vector<T, A0> named_invoke( std::vector<T, A0> lhs, append_t, std::vector<T, A1> const& rhs ) {
lhs.insert( lhs.end(), rhs.begin(), rhs.end() );
return std::move(lhs);
}
}
using my_ns::append;
std::vector<int> a {1,2,3};
std::vector<int> b {4,5,6};
auto c = a *append* b;
यहाँ मुख्य यह है कि हम एक append
के प्रकार को परिभाषित करते हैं append_t:named_operator::make_operator<append_t>
।
फिर हम दाएं और बाएं प्रकार के लिए नाम_invoke (lhs, append_t, rhs) को ओवरलोड करते हैं।
लायब्रेरी ओवर lhs*append_t
, एक अस्थायी half_apply
ऑब्जेक्ट half_apply
है। यह named_invoke( lhs, append_t, rhs )
को कॉल करने के लिए half_apply*rhs
ओवरलोड half_apply*rhs
।
हमें बस उचित append_t
टोकन बनाना होगा और उचित हस्ताक्षर के named_invoke
अनुकूल named_invoke
करना होगा, और सब कुछ हुक और काम करता है।
अधिक जटिल उदाहरण के लिए, मान लीजिए कि आप किसी std के तत्वों का तत्व-वार गुणन करना चाहते हैं :: सरणी:
template<class=void, std::size_t...Is>
auto indexer( std::index_sequence<Is...> ) {
return [](auto&& f) {
return f( std::integral_constant<std::size_t, Is>{}... );
};
}
template<std::size_t N>
auto indexer() { return indexer( std::make_index_sequence<N>{} ); }
namespace my_ns {
struct e_times_t : named_operator::make_operator<e_times_t> {};
constexpr e_times_t e_times{};
template<class L, class R, std::size_t N,
class Out=std::decay_t<decltype( std::declval<L const&>()*std::declval<R const&>() )>
>
std::array<Out, N> named_invoke( std::array<L, N> const& lhs, e_times_t, std::array<R, N> const& rhs ) {
using result_type = std::array<Out, N>;
auto index_over_N = indexer<N>();
return index_over_N([&](auto...is)->result_type {
return {{
(lhs[is] * rhs[is])...
}};
});
}
}
यह तत्व-वार सरणी कोड tuples या जोड़े या C- शैली सरणियों, या यहां तक कि चर लंबाई कंटेनरों पर काम करने के लिए बढ़ाया जा सकता है यदि आप तय करते हैं कि यदि लंबाई मेल नहीं खाती है तो क्या करें।
आप तत्व-वार ऑपरेटर प्रकार भी प्राप्त कर सकते हैं और lhs *element_wise<'+'>* rhs
।
*dot*
और *cross*
उत्पाद संचालक लिखना भी स्पष्ट उपयोग हैं।
अन्य का उपयोग करने के लिए *
का उपयोग बढ़ाया जा सकता है, जैसे +
। परिधि का नामांक संचालक नामांकित संचालक के पूर्वाभास को निर्धारित करता है, जो महत्वपूर्ण हो सकता है जब भौतिकी समीकरणों को C ++ पर अतिरिक्त ()
न्यूनतम उपयोग के साथ अनुवाद किया जाए।
ऊपर की लाइब्रेरी में थोड़े बदलाव के साथ, हम समर्थन कर सकते हैं ->*then*
ऑपरेटर और std::function
विस्तार करें std::function
अपडेट किए जा रहे मानक से पहले std::function
, या मोनडिक ->*bind*
लिखें। यह भी एक स्टेटफुल नामित ऑपरेटर, जहां हम ध्यान से पारित हो सकता था Op
अंतिम आह्वान कार्य करने के लिए नीचे, की अनुमति:
named_operator<'*'> append = [](auto lhs, auto&& rhs) {
using std::begin; using std::end;
lhs.insert( end(lhs), begin(rhs), end(rhs) );
return std::move(lhs);
};
C ++ 17 में एक नामित कंटेनर-अपेंडिंग ऑपरेटर उत्पन्न करना।