2009-07-23 23 views
176

के रूप में पारित किया गया है मैं तर्क के रूप में सी ++ टेम्पलेट फ़ंक्शन को पार करने वाले नियमों की तलाश में हूं।फ़ंक्शन टेम्पलेट तर्क

यह सी के द्वारा समर्थित है ++ के रूप में यहां एक उदाहरण के द्वारा दिखाया:

#include <iostream> 

void add1(int &v) 
{ 
    v+=1; 
} 

void add2(int &v) 
{ 
    v+=2; 
} 

template <void (*T)(int &)> 
void doOperation() 
{ 
    int temp=0; 
    T(temp); 
    std::cout << "Result is " << temp << std::endl; 
} 

int main() 
{ 
    doOperation<add1>(); 
    doOperation<add2>(); 
} 

इस तकनीक के बारे में सीखना मुश्किल है, लेकिन। Googling for "function as a template argument" ज्यादा नहीं लेता है। और क्लासिक C++ Templates The Complete Guide आश्चर्यजनक रूप से भी इस पर चर्चा नहीं करता है (कम से कम मेरी खोज से नहीं)।

मेरे पास प्रश्न हैं कि यह वैध C++ (या केवल कुछ व्यापक रूप से समर्थित एक्सटेंशन) है या नहीं।

इसके अलावा, क्या इस तरह के टेम्पलेट आमंत्रण के दौरान स्पष्ट कार्यों के साथ एक ही हस्ताक्षर के साथ एक मज़ेदार को एक दूसरे के साथ उपयोग करने की अनुमति देने का कोई तरीका है?

निम्नलिखित उपरोक्त प्रोग्राम में कम से कम Visual C++ में काम करता है, क्योंकि वाक्यविन्यास स्पष्ट रूप से गलत है। यदि आप एक कस्टम तुलना ऑपरेशन को परिभाषित करना चाहते हैं तो एक फंक्शन पॉइंटर या फ़ैक्टर को std :: sort algorithm पर पारित करने के तरीके के समान, यह एक अच्छा फ़ंक्शन के लिए फ़ंक्शन को स्विच करने में सक्षम होना अच्छा होगा। सी ++ टेम्पलेट्स किताब में एक वेब लिंक या दो, या एक पृष्ठ पर

struct add3 { 
     void operator() (int &v) {v+=3;} 
    }; 
... 

    doOperation<add3>(); 

प्वाइंटर की सराहना की होगी!

+0

टेम्पलेट तर्क के रूप में एक समारोह का क्या लाभ है साथ मेरे लिए काम किया? रिटर्न प्रकार टेम्पलेट प्रकार का उपयोग नहीं किया जाएगा? – DaClown

उत्तर

98

हां, यह मान्य है।

template <typename F> 
void doOperation(F f) 
{ 
    int temp=0; 
    f(temp); 
    std::cout << "Result is " << temp << std::endl; 
} 

जो अब या तो के रूप में कहा जा सकता है:

doOperation(add2); 
doOperation(add3()); 

इस के साथ समस्या यह है

यह रूप में अच्छी तरह functors के साथ काम करने के लिए के रूप में, हमेशा की तरह समाधान के बजाय कुछ इस तरह है अगर यह संकलक को add2 पर कॉल इनलाइन करने के लिए मुश्किल बनाता है, क्योंकि सभी संकलक जानता है कि फ़ंक्शन पॉइंटर प्रकार void (*)(int &)doOperation पर पास किया जा रहा है। (लेकिन add3, एक मज़ेदार होने के नाते, आसानी से रेखांकित किया जा सकता है। यहां, संकलक जानता है कि add3 प्रकार का ऑब्जेक्ट फ़ंक्शन पर पास किया गया है, जिसका अर्थ है कि कॉल करने के लिए फ़ंक्शन add3::operator() है, न केवल कुछ अज्ञात फ़ंक्शन पॉइंटर।)

+17

अब यहां एक दिलचस्प सवाल है। फ़ंक्शन नाम पारित होने पर, ऐसा नहीं होता है कि फ़ंक्शन पॉइंटर शामिल है। संकलन समय पर दिया गया यह एक स्पष्ट कार्य है। तो संकलक जानता है कि यह संकलन समय पर क्या मिला है। – SPWorley

+0

फ़ंक्शन पॉइंटर बनाने के लिए टेम्पलेट अधिभार का उपयोग किया जा सकता है और फ़ैक्टर मामलों में समान वाक्यविन्यास होता है? क्या कोई प्रदर्शन लाभ होगा? विशेष रूप से स्टेटलेस फ़ैक्टर के लिए जो प्रभावी रूप से कोई उदाहरण नहीं रखते हैं और इसलिए हम किसी भी मामले से बचना चाहते हैं जहां संकलक इनलाइनिंग और अनुकूलन को रोक सकता है। – SPWorley

+1

फंक्शन पॉइंटर्स पर फ़ैक्टर का उपयोग करने का एक फायदा है। मज़ेदार को कक्षा के अंदर instanciated किया जा सकता है और इस प्रकार अनुकूलन के लिए संकलक (जैसे inlining) के लिए अधिक संचालन प्रदान करता है। फ़ंक्शन पॉइंटर पर कॉल को अनुकूलित करने के लिए कंपाइलर को दबाया जाएगा। –

1

आपके मज़ेदार उदाहरण काम नहीं करने का कारण यह है कि आपको operator() का आह्वान करने के लिए एक उदाहरण की आवश्यकता है।

8

अपने टेम्पलेट

template <void (*T)(int &)> 
void doOperation() 

पैरामीटर T में एक गैर प्रकार टेम्पलेट पैरामीटर है। इसका मतलब है कि टेम्पलेट फ़ंक्शन का व्यवहार पैरामीटर के मान के साथ बदलता है (जिसे संकलन समय पर तय किया जाना चाहिए, जो पॉइंटर स्थिरांक कार्य करता है)।

यदि आप कुछ काम करना चाहते हैं जो फ़ंक्शन ऑब्जेक्ट्स और फ़ंक्शन पैरामीटर दोनों के साथ काम करता है तो आपको एक टाइप टेम्पलेट की आवश्यकता होती है। जब आप ऐसा करते हैं, हालांकि, आपको रन टाइम पर फ़ंक्शन में ऑब्जेक्ट इंस्टेंस (या तो फ़ंक्शन ऑब्जेक्ट इंस्टेंस या फ़ंक्शन पॉइंटर) प्रदान करने की आवश्यकता होती है।

template <class T> 
void doOperation(T t) 
{ 
    int temp=0; 
    t(temp); 
    std::cout << "Result is " << temp << std::endl; 
} 

कुछ मामूली प्रदर्शन विचार हैं। यह नया संस्करण फ़ंक्शन पॉइंटर तर्कों के साथ कम कुशल हो सकता है क्योंकि विशेष फ़ंक्शन पॉइंटर केवल समय पर ही खराब हो जाता है और रन टाइम पर कॉल किया जाता है जबकि आपके फ़ंक्शन पॉइंटर टेम्पलेट को विशेष फ़ंक्शन पॉइंटर के आधार पर अनुकूलित किया जा सकता है (संभवतः फ़ंक्शन कॉल इनलाइन)। फ़ंक्शन ऑब्जेक्ट्स को अक्सर टाइप किए गए टेम्पलेट के साथ बहुत कुशलता से विस्तारित किया जा सकता है, हालांकि विशेष operator() फ़ंक्शन ऑब्जेक्ट के प्रकार से पूरी तरह से निर्धारित होता है।

0

संपादित करें: ऑपरेटर को संदर्भ के रूप में पास करना काम नहीं करता है। सादगी के लिए, इसे फ़ंक्शन पॉइंटर के रूप में समझें। आप सिर्फ सूचक को भेजते हैं, संदर्भ नहीं। मुझे लगता है कि आप इस तरह कुछ लिखने की कोशिश कर रहे हैं।

struct Square 
{ 
    double operator()(double number) { return number * number; } 
}; 

template <class Function> 
double integrate(Function f, double a, double b, unsigned int intervals) 
{ 
    double delta = (b - a)/intervals, sum = 0.0; 

    while(a < b) 
    { 
     sum += f(a) * delta; 
     a += delta; 
    } 

    return sum; 
} 

। ।

std::cout << "interval : " << i << tab << tab << "intgeration = " 
<< integrate(Square(), 0.0, 1.0, 10) << std::endl; 
53

टेम्पलेट पैरामीटर या तो टाइप (टाइपनाम टी) या मूल्य (int एक्स) द्वारा पैरामीटरकृत किया जा सकता है।

कोड का एक टुकड़ा टेम्पलेट करने का "पारंपरिक" सी ++ तरीका एक मज़ेदार का उपयोग करना है - यानी, कोड किसी ऑब्जेक्ट में है, और ऑब्जेक्ट इस प्रकार कोड को अद्वितीय प्रकार देता है।

पारंपरिक कार्यों के साथ काम करते समय, यह तकनीक अच्छी तरह से काम नहीं करती है, क्योंकि प्रकार में परिवर्तन विशिष्ट फ़ंक्शन का संकेत नहीं देता है - बल्कि यह केवल कई संभावित कार्यों के हस्ताक्षर को निर्दिष्ट करता है। तो:

template<typename OP> 
int do_op(int a, int b, OP op) 
{ 
    return op(a,b); 
} 
int add(int a, int b) { return a + b; } 
... 

int c = do_op(4,5,add); 

फ़ैक्टर मामले के बराबर नहीं है। इस उदाहरण में, do_op को सभी फ़ंक्शन पॉइंटर्स के लिए तत्काल किया जाता है जिनके हस्ताक्षर int X (int, int) है। इस मामले को पूरी तरह से इनलाइन करने के लिए संकलक को बहुत आक्रामक होना होगा। (। मैं इसे हालांकि इंकार नहीं, के रूप में संकलक अनुकूलन बहुत उन्नत हो गया है)

एक तरीका यह बताने के लिए कि इस कोड काफी हम क्या चाहते हैं नहीं करता है:

int (* func_ptr)(int, int) = add; 
int c = do_op(4,5,func_ptr); 

अभी भी कानूनी है , और स्पष्ट रूप से यह रेखांकित नहीं हो रहा है। पूर्ण इनलाइनिंग प्राप्त करने के लिए, हमें मूल्य से टेम्पलेट की आवश्यकता है, इसलिए फ़ंक्शन टेम्पलेट में पूरी तरह से उपलब्ध है।

typedef int(*binary_int_op)(int, int); // signature for all valid template params 
template<binary_int_op op> 
int do_op(int a, int b) 
{ 
return op(a,b); 
} 
int add(int a, int b) { return a + b; } 
... 
int c = do_op<add>(4,5); 

इस मामले में, do_op से प्रत्येक instantiated संस्करण एक विशेष समारोह के साथ पहले से ही उपलब्ध instantiated है। इस प्रकार हम उम्मीद करते हैं कि do_op के लिए कोड "ए + बी वापस लौटें" जैसा दिखता है। (लिस्प प्रोग्रामर, अपने smirking रोक!)

हम यह भी पुष्टि कर सकते हैं कि इस हम क्या इस वजह से चाहते के करीब है:

int (* func_ptr)(int,int) = add; 
int c = do_op<func_ptr>(4,5); 

संकलित करने के लिए असफल हो जायेगी। जीसीसी का कहना है: "त्रुटि: 'func_ptr' निरंतर अभिव्यक्ति में प्रकट नहीं हो सकता है। दूसरे शब्दों में, मैं पूरी तरह से do_op का विस्तार नहीं कर सकता क्योंकि आपने मुझे यह जानने के लिए संकलक समय पर पर्याप्त जानकारी नहीं दी है कि हमारा ऑप क्या है।

तो यदि दूसरा उदाहरण वास्तव में हमारे सेशन को पूरी तरह से रेखांकित कर रहा है, और पहला नहीं है, तो टेम्पलेट कितना अच्छा है? यह क्या कर रहा है? जवाब है: टाइप जबरन।पहले उदाहरण पर यह रिफ काम करेगा:

template<typename OP> 
int do_op(int a, int b, OP op) { return op(a,b); } 
float fadd(float a, float b) { return a+b; } 
... 
int c = do_op(4,5,fadd); 

वह उदाहरण काम करेगा! (मैं यह सुझाव नहीं दे रहा हूं कि यह अच्छा सी ++ है लेकिन ...) क्या हुआ है, विभिन्न कार्यों के हस्ताक्षर के आसपास do_op को templated किया गया है, और प्रत्येक अलग तत्काल विभिन्न प्रकार के जबरन कोड लिखेंगे। तो Fadd साथ do_op के लिए instantiated कोड तरह दिखता है:

convert a and b from int to float. 
call the function ptr op with float a and float b. 
convert the result back to int and return it. 

तुलना करके, हमारे द्वारा मूल्य मामले समारोह तर्क का सटीक मिलान की आवश्यकता है।

+1

http://stackoverflow.com/questions/13674935/why-is-it-clear-that-a-template-function-instantiation-will-not-be- सीधे प्रतिक्रिया में अनुवर्ती प्रश्न के लिए उल्लिखित देखें यहां अवलोकन है कि 'int c = do_op (4,5, func_ptr); 'स्पष्ट रूप से इनलाइन नहीं हो रहा है"। –

+0

इनलाइनों के उदाहरण के लिए यहां देखें: http://stackoverflow.com/questions/4860762/c-can-compilers-inline-a-function- सूचक लगता है कि आजकल कंपाइलर्स बहुत स्मार्ट हो रहे हैं। – BigSandwich

7

फ़ंक्शन पॉइंटर्स टेम्पलेट पैरामीटर के रूप में पारित किए जा सकते हैं, और this is part of standard C++ । हालांकि टेम्पलेट में उन्हें पॉइंटर-टू-फ़ंक्शन के बजाए फ़ंक्शन के रूप में घोषित और उपयोग किया जाता है। टेम्पलेट तत्काल पर एक नाम के बजाय फ़ंक्शन का पता पास करता है।

उदाहरण के लिए:

int i; 


void add1(int& i) { i += 1; } 

template<void op(int&)> 
void do_op_fn_ptr_tpl(int& i) { op(i); } 

i = 0; 
do_op_fn_ptr_tpl<&add1>(i); 

आप किसी टेम्पलेट तर्क के रूप में एक functor प्रकार पास करना चाहते हैं:

template<typename op> 
void do_op_fntr_arg(int& i, op o) { o(i); } 

i = 0; 
add2_t add2; 

// This has the advantage of looking identical whether 
// you pass a functor or a free function: 
do_op_fntr_arg(i, add1); 
do_op_fntr_arg(i, add2); 
:

struct add2_t { 
    void operator()(int& i) { i += 2; } 
}; 

template<typename op> 
void do_op_fntr_tpl(int& i) { 
    op o; 
    o(i); 
} 

i = 0; 
do_op_fntr_tpl<add2_t>(i); 

कई जवाब एक तर्क के रूप एक functor उदाहरण पारित

टेम्पलेट तर्क के साथ आप इस समान उपस्थिति में सबसे नज़दीक हो सकते हैंको परिभाषित करना हैदो बार- एक बार गैर-प्रकार पैरामीटर के साथ और एक बार पैरामीटर के साथ।

// non-type (function pointer) template parameter 
template<void op(int&)> 
void do_op(int& i) { op(i); } 

// type (functor class) template parameter 
template<typename op> 
void do_op(int& i) { 
    op o; 
    o(i); 
} 

i = 0; 
do_op<&add1>(i); // still need address-of operator in the function pointer case. 
do_op<add2_t>(i); 

ईमानदारी से, मैं वास्तव में इस संकलित करने के लिए नहीं की उम्मीद , लेकिन यह जीसीसी-4.8 और विजुअल स्टूडियो 2013

संबंधित मुद्दे