संकलन सदस्य-कार्य आत्मनिरीक्षण के इस प्रश्न का स्वीकृत उत्तर, हालांकि यह उचित रूप से लोकप्रिय है, इसमें एक रोड़ा है जिसे निम्नलिखित कार्यक्रम में देखा जा सकता है:
#include <type_traits>
#include <iostream>
#include <memory>
/* Here we apply the accepted answer's technique to probe for the
the existence of `E T::operator*() const`
*/
template<typename T, typename E>
struct has_const_reference_op
{
template<typename U, E (U::*)() const> struct SFINAE {};
template<typename U> static char Test(SFINAE<U, &U::operator*>*);
template<typename U> static int Test(...);
static const bool value = sizeof(Test<T>(0)) == sizeof(char);
};
using namespace std;
/* Here we test the `std::` smart pointer templates, including the
deprecated `auto_ptr<T>`, to determine in each case whether
T = (the template instantiated for `int`) provides
`int & T::operator*() const` - which all of them in fact do.
*/
int main(void)
{
cout << has_const_reference_op<auto_ptr<int>,int &>::value;
cout << has_const_reference_op<unique_ptr<int>,int &>::value;
cout << has_const_reference_op<shared_ptr<int>,int &>::value << endl;
return 0;
}
जीसीसी 4.6.3 के साथ बनाया गया है, कार्यक्रम आउटपुट 110
- हमें बताए कि
T = std::shared_ptr<int>
है नहीं प्रदान करते हैं int & T::operator*() const
।
यदि आप इस गोच में पहले से ही समझदार नहीं हैं, तो std::shared_ptr<T>
हेडर की परिभाषा पर एक नज़र
<memory>
प्रकाश डालेगी। उस कार्यान्वयन में, std::shared_ptr<T>
एक आधार वर्ग से लिया गया है जिसमें से यह विरासत में मिला है operator*() const
। तो टेम्पलेट इन्स्टेन्शियशन
SFINAE<U, &U::operator*>
कि गठन के लिए ऑपरेटर "की खोज"
U = std::shared_ptr<T>
, नहीं होगा क्योंकि std::shared_ptr<T>
कोई है
operator*()
अपने आप में और टेम्पलेट इन्स्टेन्शियशन नहीं "विरासत करना" है।
यह रोड़ा "ज्ञात आकार () ट्रिक" का उपयोग करते हुए, केवल T
कुछ सदस्य फ़ंक्शन का पता लगाने के लिए, प्रसिद्ध SFINAE दृष्टिकोण को प्रभावित नहीं करता है mf
(उदाहरण के लिए
यह उत्तर और टिप्पणियां देखें)। लेकिन यह स्थापित T::mf
करना अक्सर मौजूद होता है (आमतौर पर?) पर्याप्त अच्छा नहीं है: आपको यह भी स्थापित करने की आवश्यकता हो सकती है कि इसमें एक वांछित हस्ताक्षर है। यह वह जगह है जहाँ सचित्र तकनीक स्कोर है। वांछित हस्ताक्षर का सूचक संस्करण एक टेम्पलेट प्रकार के पैरामीटर में खुदा हुआ है जिसे &T::mf
सफल होने के लिए SFINAE जांच के लिए संतुष्ट होना चाहिए
। लेकिन यह टेम्प्लेट इंस्टेंटिंग तकनीक T::mf
विरासत में मिलने पर गलत उत्तर देती है।
जीवन भर आत्मनिरीक्षण के लिए एक सुरक्षित SFINAE तकनीक एक प्रकार के तर्क T::mf
के उपयोग से बचना चाहिए &T::mf
, जिस पर SFINAE फ़ंक्शन टेम्पलेट रिज़ॉल्यूशन निर्भर करता है। इसके बजाय, SFINAE टेम्पलेट फ़ंक्शन रिज़ॉल्यूशन ओवरलोडेड SFINAE जांच फ़ंक्शन के तर्क प्रकार के रूप में उपयोग किए जाने वाले सटीक प्रकार के घोषणाओं पर निर्भर कर सकता है।
सवाल यह है कि इस बाधा का पालन करता है मैं के compiletime पता लगाने के लिए उदाहरण देकर स्पष्ट कर देंगे करने के लिए एक जवाब के माध्यम से E T::operator*() const
मनमाना के लिए, T
और E
। एक ही पैटर्न
किसी अन्य सदस्य विधि हस्ताक्षर के लिए जांच करने के लिए उत्परिवर्ती उत्परिवर्तन लागू करेगा ।
#include <type_traits>
/*! The template `has_const_reference_op<T,E>` exports a
boolean constant `value that is true iff `T` provides
`E T::operator*() const`
*/
template< typename T, typename E>
struct has_const_reference_op
{
/* SFINAE operator-has-correct-sig :) */
template<typename A>
static std::true_type test(E (A::*)() const) {
return std::true_type();
}
/* SFINAE operator-exists :) */
template <typename A>
static decltype(test(&A::operator*))
test(decltype(&A::operator*),void *) {
/* Operator exists. What about sig? */
typedef decltype(test(&A::operator*)) return_type;
return return_type();
}
/* SFINAE game over :( */
template<typename A>
static std::false_type test(...) {
return std::false_type();
}
/* This will be either `std::true_type` or `std::false_type` */
typedef decltype(test<T>(0,0)) type;
static const bool value = type::value; /* Which is it? */
};
इस समाधान में, अधिभार SFINAE जांच फ़ंक्शन test()
"पुनरावर्ती रूप से लागू किया गया" है। (बेशक यह वास्तव में बिल्कुल नहीं लगाया गया है; इसमें केवल संकलक द्वारा हल किए गए काल्पनिक चालान के रिटर्न प्रकार हैं।)
हमें कम से कम एक और अधिकतम दो बिंदुओं पर जानकारी के लिए जांच करने की आवश्यकता है:
- करता है
T::operator*()
सब पर मौजूद हैं? यदि नहीं, तो हम कर रहे हैं।
- यह देखते हुए कि
T::operator*()
क्या इसके हस्ताक्षर मौजूद हैं
E T::operator*() const
?
हम एकल कॉल के रिटर्न प्रकार का मूल्यांकन करके उत्तर प्राप्त करते हैं test(0,0)
। इसके द्वारा किया जाता है:
typedef decltype(test<T>(0,0)) type;
यह कॉल /* SFINAE operator-exists :) */
ओवरलोड का समाधान हो सकता है test()
, या यह ओवरलोड का समाधान हो सकता है /* SFINAE game over :( */
। यह /* SFINAE operator-has-correct-sig :) */
अधिभार को हल नहीं कर सकता है , क्योंकि एक को सिर्फ एक तर्क की उम्मीद है और हम दो को पारित कर रहे हैं।
हम दो क्यों गुजर रहे हैं? केवल प्रस्ताव को बाहर करने के लिए मजबूर करने के लिए
/* SFINAE operator-has-correct-sig :) */
। दूसरे तर्क का कोई अन्य लक्षण नहीं है।
यह कॉल केवल उस स्थिति में test(0,0)
हल होगी /* SFINAE operator-exists :) */
जब पहला तर्क 0 उस अधिभार के पहले पैरामीटर प्रकार को संतृप्त करता है, जो कि decltype(&A::operator*)
, के साथ है A = T
। 0 केवल उस स्थिति में संतुष्ट होगा जैसे मामला T::operator*
मौजूद है।
मान लीजिए कि कंपाइलर ने हां को हां कहा है। फिर यह साथ जा रहा है
/* SFINAE operator-exists :) */
और इसे फ़ंक्शन कॉल के रिटर्न प्रकार को निर्धारित करने की आवश्यकता है, जो उस स्थिति में है decltype(test(&A::operator*))
- अभी तक किसी अन्य कॉल का रिटर्न प्रकार test()
।
इस बार, हम केवल एक तर्क दे &A::operator*
रहे हैं, जिसे अब हम जानते हैं कि हम मौजूद हैं, या हम यहाँ नहीं होंगे। एक कॉल test(&A::operator*)
या तो /* SFINAE operator-has-correct-sig :) */
फिर से हल हो सकती है या फिर हल हो सकती है /* SFINAE game over :( */
। कॉल से मेल खाएगी
/* SFINAE operator-has-correct-sig :) */
सिर्फ मामले में &A::operator*
है कि अधिभार है, जिनमें से एक पैरामीटर प्रकार को संतुष्ट करता है E (A::*)() const
के साथ, A = T
।
कंपाइलर यहां हां कहेगा यदि T::operator*
उसके पास वांछित हस्ताक्षर हैं, और फिर फिर से अधिभार के प्रकार का मूल्यांकन करना होगा। अब और नहीं "पुनरावर्तन": यह है std::true_type
।
यदि कंपाइलर /* SFINAE operator-exists :) */
कॉल के
लिए test(0,0)
चयन नहीं करता है या कॉल के लिए नहीं चुनता है , तो किसी भी स्थिति में यह साथ जाता है
और अंतिम रिटर्न प्रकार होता है ।/* SFINAE operator-has-correct-sig :) */
test(&A::operator*)
/* SFINAE game over :( */
std::false_type
यहां एक परीक्षण कार्यक्रम है जो विभिन्न मामलों के नमूने (जीसीसी 4.6.3 फिर से) में अपेक्षित उत्तरों का निर्माण करने वाले टेम्पलेट को दिखाता है।
// To test
struct empty{};
// To test
struct int_ref
{
int & operator*() const {
return *_pint;
}
int & foo() const {
return *_pint;
}
int * _pint;
};
// To test
struct sub_int_ref : int_ref{};
// To test
template<typename E>
struct ee_ref
{
E & operator*() {
return *_pe;
}
E & foo() const {
return *_pe;
}
E * _pe;
};
// To test
struct sub_ee_ref : ee_ref<char>{};
using namespace std;
#include <iostream>
#include <memory>
#include <vector>
int main(void)
{
cout << "Expect Yes" << endl;
cout << has_const_reference_op<auto_ptr<int>,int &>::value;
cout << has_const_reference_op<unique_ptr<int>,int &>::value;
cout << has_const_reference_op<shared_ptr<int>,int &>::value;
cout << has_const_reference_op<std::vector<int>::iterator,int &>::value;
cout << has_const_reference_op<std::vector<int>::const_iterator,
int const &>::value;
cout << has_const_reference_op<int_ref,int &>::value;
cout << has_const_reference_op<sub_int_ref,int &>::value << endl;
cout << "Expect No" << endl;
cout << has_const_reference_op<int *,int &>::value;
cout << has_const_reference_op<unique_ptr<int>,char &>::value;
cout << has_const_reference_op<unique_ptr<int>,int const &>::value;
cout << has_const_reference_op<unique_ptr<int>,int>::value;
cout << has_const_reference_op<unique_ptr<long>,int &>::value;
cout << has_const_reference_op<int,int>::value;
cout << has_const_reference_op<std::vector<int>,int &>::value;
cout << has_const_reference_op<ee_ref<int>,int &>::value;
cout << has_const_reference_op<sub_ee_ref,int &>::value;
cout << has_const_reference_op<empty,int &>::value << endl;
return 0;
}
क्या इस विचार में नई खामियां हैं? क्या यह एक बार फिर गिरने से बचने के लिए अधिक सामान्य हो सकता है?