टेम्पलेट तर्क के रूप में कार्य किया गया


224

मैं C ++ टेम्प्लेट को पास करने के नियमों को तर्क के रूप में देख रहा हूं।

यह C ++ द्वारा समर्थित है जैसा कि यहां एक उदाहरण द्वारा दिखाया गया है:

#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>();
}

हालाँकि इस तकनीक के बारे में सीखना मुश्किल है। "टेम्पलेट तर्क के रूप में फ़ंक्शन" के लिए गुग्लिंग करने से बहुत कुछ नहीं होता है। और क्लासिक सी ++ टेम्पलेट्स पूरी गाइड आश्चर्यजनक रूप से भी इस पर चर्चा नहीं करती है (कम से कम मेरी खोज से नहीं)।

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

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

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

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

    doOperation<add3>();

एक वेब लिंक या दो, या C ++ टेम्प्लेट बुक में एक पृष्ठ की ओर इशारा किया जाएगा!


टेम्पलेट तर्क के रूप में एक फ़ंक्शन का क्या लाभ है? क्या वापसी प्रकार टेम्पलेट प्रकार का उपयोग नहीं किया जाएगा?
21-13 को DaClown

संबंधित: कोई कैप्चर के साथ एक लंबो एक फ़ंक्शन पॉइंटर को क्षय कर सकता है, और आप इसे C ++ 17 में टेम्पलेट परम के रूप में पास कर सकते हैं। क्लैंग इसे ठीक करता है, लेकिन वर्तमान gcc (8.2) में एक बग है और इसे "कोई लिंकेज" नहीं होने के रूप में गलत तरीके से अस्वीकार करता है -std=gnu++17क्या मैं फ़ंक्शन पॉइंटर टेम्पलेट नॉन-टाइप तर्क के रूप में C ++ 17 कैप्चरलेस लैम्ब्डा कॉन्स्ट्रेप रूपांतरण ऑपरेटर के परिणाम का उपयोग कर सकता हूं?
पीटर कॉर्डेस

जवाबों:


123

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

जैसा कि यह फंक्शनलर्स के साथ भी काम करता है, सामान्य समाधान कुछ इस तरह है:

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

जिसे अब कहा जा सकता है:

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

इसे लाइव देखें

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


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

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

11
जब फंक्शन को टेम्प्लेट पैरामीटर में उपयोग किया जाता है तो यह पास किए गए फंक्शन में एक पॉइंटर में 'डिसेबल' हो जाता है। मापदंडों के तर्कों के रूप में पारित होने पर यह कैसे संकेत देता है कि सरणियों में क्षय होता है, यह विनम्र है। बेशक, सूचक मान को संकलन समय पर जाना जाता है और इसे बाहरी लिंकेज के साथ एक फ़ंक्शन को इंगित करना चाहिए ताकि कंपाइलर अनुकूलन उद्देश्यों के लिए इस जानकारी का उपयोग कर सके।
CB बेली

5
कुछ वर्षों के बाद तेजी से आगे बढ़े, C तले 11 में खाका तर्कों के रूप में कार्यों का उपयोग करने की स्थिति में सुधार हुआ। अब आप फ़ंक्शंस कक्षाओं जैसे जावािज़्म का उपयोग करने के लिए बाध्य नहीं हैं, और उदाहरण के लिए स्थैतिक इनलाइन फ़ंक्शंस का उपयोग सीधे टेम्प्लेट तर्क के रूप में कर सकते हैं। अभी भी 1970 के लिस्प मैक्रोज़ की तुलना में अभी तक रोना है, लेकिन सी ++ 11 निश्चित रूप से वर्षों में अच्छी प्रगति है।
पफल्कॉन

5
चूँकि c ++ 11 फंक्शन को rvalue reference ( template <typename F> void doOperation(F&& f) {/**/}) के रूप में लेना बेहतर नहीं होगा , इसलिए उदाहरण के लिए इसे बाइंड करने के बजाय बाइंड-एक्सप्रेशन पास कर सकते हैं?
user1810087

70

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

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

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

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);

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

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 के प्रत्येक तात्कालिक संस्करण को पहले से उपलब्ध एक विशिष्ट फ़ंक्शन के साथ त्वरित किया जाता है। इस प्रकार हम उम्मीद करते हैं कि do_op के लिए कोड "रिटर्न ए + बी" की तरह दिखाई देगा। (लिस्प प्रोग्रामर्स, अपने धूम्रपान बंद करो!)

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

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);

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

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.

तुलना करके, हमारे बाय-वैल्यू मामले को फ़ंक्शन तर्कों पर सटीक मिलान की आवश्यकता होती है।


2
इस अवलोकन के प्रत्यक्ष उत्तर में एक अनुवर्ती प्रश्न के लिए stackoverflow.com/questions/13674935/… देखें, जो int c = do_op(4,5,func_ptr);"स्पष्ट रूप से इनलेट नहीं हो रहा है"।
दान निसानबाम

इनलेटेड होने के उदाहरण के लिए यहां देखें: stackoverflow.com/questions/4860762/… इन दिनों लगता है कि कंपाइलर बहुत स्मार्ट हो रहे हैं।
बिगसैंडविच

15

फ़ंक्शन पॉइंटर्स को टेम्प्लेट मापदंडों के रूप में पारित किया जा सकता है, और यह मानक सी ++ का हिस्सा है । हालाँकि टेम्प्लेट में इन्हें पॉइंटर-टू-फंक्शन के बजाय घोषित और उपयोग किया जाता है। टेम्पलेट तात्कालिकता में केवल नाम के बजाय फ़ंक्शन का पता गुजरता है।

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

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);

यदि आप एक टेम्पलेट तर्क के रूप में एक फंक्शनल टाइप पास करना चाहते हैं:

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);

कई उत्तर एक तर्क के रूप में एक फंतासी उदाहरण देते हैं:

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);

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

// 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);

ईमानदारी से, मुझे वास्तव में उम्मीद थी कि यह संकलन नहीं होगा, लेकिन इसने मेरे लिए gcc-4.8 और Visual Studio 2013 के साथ काम किया।


9

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

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()से फ़ंक्शन ऑब्जेक्ट के प्रकार द्वारा पूरी तरह से निर्धारित किया जाता है।


1

आपके फ़नट्रक्टर उदाहरण के काम नहीं करने का कारण यह है कि आपको इनवाइट करने के लिए एक उदाहरण की आवश्यकता होती है 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;
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.