ফাংশনটি টেমপ্লেট আর্গুমেন্ট হিসাবে পাস হয়েছে


224

আমি আর্গুমেন্ট হিসাবে সি ++ টেমপ্লেট ফাংশনগুলি সহ জড়িত বিধিগুলি সন্ধান করছি।

এটি উদাহরণস্বরূপ প্রদর্শিত হিসাবে সি ++ দ্বারা সমর্থিত:

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

তবে এই কৌশল সম্পর্কে শেখা কঠিন। "টেমপ্লেট আর্গুমেন্ট হিসাবে ফাংশন" এর জন্য গুগলিং খুব বেশি নেতৃত্ব দেয় না। এবং ক্লাসিক সি ++ টেমপ্লেটস সম্পূর্ণ গাইডটি আশ্চর্যরকমভাবে এটিও আলোচনা করে না (কমপক্ষে আমার অনুসন্ধান থেকে নয়)।

আমার কাছে থাকা প্রশ্নগুলি হ'ল এটি বৈধ সি ++ (অথবা কেবলমাত্র কিছু বিস্তৃত সমর্থিত এক্সটেনশন)।

এছাড়াও, এই ধরণের টেম্পলেট আহরণের সময় একই স্বাক্ষরযুক্ত কোনও ফান্টরকে স্বতন্ত্র ফাংশনগুলির সাথে আন্তঃব্যবহারযোগ্যভাবে ব্যবহার করার অনুমতি দেওয়ার কোনও উপায় আছে কি?

নিম্নোক্ত উপরের প্রোগ্রামটিতে কমপক্ষে ভিজ্যুয়াল সি ++ এ কাজ করে না , কারণ বাক্য গঠনটি অবশ্যই স্পষ্টত ভুল। আপনি যদি কোনও কাস্টম তুলনা ক্রিয়াকলাপটি সংজ্ঞায়িত করতে চান তবে কোনও ফাংশন পয়েন্টার বা স্টান্টারের সাথে আপনি যেমন স্ট্যান্ড :: সাজান অ্যালগরিদমকে পাস করতে পারবেন তার অনুরূপ কোনও ফান্টারের জন্য কোনও ফাংশনটি স্যুইচআউট করতে সক্ষম হতে পেরে ভাল লাগবে sort

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

    doOperation<add3>();

একটি ওয়েব লিঙ্ক বা দুটি, বা সি ++ টেমপ্লেটস বইয়ের একটি পৃষ্ঠায় পয়েন্টার প্রশংসা করা হবে!


টেমপ্লেট যুক্তি হিসাবে কোনও ফাংশনটির সুবিধা কী? রিটার্ন টাইপটি কি টেম্পলেট টাইপ ব্যবহার করা হবে না?
ডক্লাউন

সম্পর্কিত: কোনও ক্যাপচারবিহীন ল্যাম্বদা কোনও ফাংশন পয়েন্টারকে ক্ষয় করতে পারে এবং আপনি এটি C ++ 17 এ টেমপ্লেট পরম হিসাবে পাস করতে পারেন। ঝনঝন এটি ঠিকঠাক করে, তবে বর্তমান জিসিসি (8.2) এর একটি ত্রুটি রয়েছে এবং এটি "লিঙ্কেজ" না থাকা হিসাবে ভুলভাবে এটিকে প্রত্যাখ্যান করে -std=gnu++17আমি কি ফাংশন পয়েন্টার টেম্পলেট নন-টাইপ আর্গুমেন্ট হিসাবে সি ++ 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, ফান্টেক্টর হওয়ায় সহজেই linedোকানো যেতে পারে Here এখানে, সংকলকটি জানে যে টাইপের একটি অবজেক্টটি add3ফাংশনে প্রেরণ করা হয়, যার অর্থ কল করতে হবে যে ফাংশনটি add3::operator()কেবল কোনও অজানা ফাংশন পয়েন্টার নয়))


19
এখন এখানে একটি আকর্ষণীয় প্রশ্ন। কোনও ফাংশনের নাম পাস করার পরে, এটি কোনও ফাংশন পয়েন্টার জড়িত এমন নয়। এটি একটি সুস্পষ্ট ফাংশন, সংকলন সময়ে দেওয়া। সুতরাং সংকলকটি কম্পাইল সময়ে এটি ঠিক কী পেয়েছিল তা জানে।
এসপি ওয়ার্লি

1
ফাংশন পয়েন্টারগুলির মাধ্যমে ফান্টেক্টর ব্যবহার করার একটি সুবিধা রয়েছে। ফান্টেক্টরটি ক্লাসের অভ্যন্তরে ইনস্ট্যান্ট করা যেতে পারে এবং এইভাবে সংযোজনকারীটির জন্য অপ্টিমাইটিজেশন (যেমন ইনলাইনিং) এর জন্য আরও বেশি দক্ষতা সরবরাহ করে। সংকলকটি একটি ফাংশন পয়েন্টারের মাধ্যমে কলটি অনুকূলিত করতে কঠোরভাবে চাপ দেওয়া হবে।
মার্টিন ইয়র্ক

11
ফাংশনটি যখন কোনও টেম্পলেট প্যারামিটারে ব্যবহৃত হয় তখন এটি পাস হয়ে ফাংশনটির পয়েন্টারে 'ক্ষয়ে যায়'। প্যারামিটারগুলিতে আর্গুমেন্ট হিসাবে পাস করার সময় অ্যারের পয়েন্টারগুলিতে কীভাবে ক্ষয় হয় তা অ্যানালগাস। অবশ্যই, পয়েন্টার মানটি সংকলন সময়ে জানা যায় এবং অবশ্যই বাহ্যিক সংযোগ সহ কোনও ফাংশনে নির্দেশ করতে হবে যাতে সংকলক অনুকূলিতকরণের উদ্দেশ্যে এই তথ্যটি ব্যবহার করতে পারে।
সিবি বেইলি

5
কয়েক বছর পরে দ্রুত এগিয়ে যাওয়া, সি ++ 11 এ টেমপ্লেট আর্গুমেন্ট হিসাবে ফাংশন ব্যবহারের পরিস্থিতি অনেক উন্নত। আপনি আর ফ্যাঙ্ক্টর ক্লাসের মতো জাভা ব্যবহার করতে বাধ্য নন এবং উদাহরণস্বরূপ স্ট্যাটিক ইনলাইন ফাংশনগুলির জন্য সরাসরি টেমপ্লেট আর্গুমেন্ট হিসাবে ব্যবহার করতে পারেন। এখনও 1970 এর দশকের লিস্প ম্যাক্রোগুলির সাথে তুলনা করে অনেক কান্নাকাটি হয়েছে তবে কয়েক বছরের মধ্যে অবশ্যই সি ++ 11 ভাল অগ্রগতি পেয়েছে।
pfalcon

5
যেহেতু সি ++ 11 ফাংশনটিকে যথাযথ রেফারেন্স ( template <typename F> void doOperation(F&& f) {/**/}) হিসাবে গ্রহণ করা ভাল হবে না , তাই উদাহরণস্বরূপ বাঁধাই বাঁধাইয়ের পরিবর্তে একটি বাইন্ড-এক্সপ্রেশনটি পাস করতে পারে?
ব্যবহারকারী 1810087

70

টেমপ্লেট প্যারামিটারগুলি হয় টাইপ (টাইপনেম টি) বা মান (ইন্ট এক্স) দ্বারা প্যারামিটারাইজড হতে পারে।

কোডের টুকরো টেম্প্লেট করার "traditionalতিহ্যবাহী" সি ++ পন্থাটি একটি ফান্টেক্টর ব্যবহার করা - অর্থাৎ কোডটি কোনও বস্তুতে থাকে এবং অবজেক্টটি কোডটিকে অনন্য প্রকার দেয়।

Traditionalতিহ্যগত ফাংশনগুলির সাথে কাজ করার সময়, এই কৌশলটি ভালভাবে কাজ করে না, কারণ প্রকারের পরিবর্তনটি একটি নির্দিষ্ট ক্রিয়াকে নির্দেশ করে না - বরং এটি কেবলমাত্র অনেকগুলি সম্ভাব্য কার্যকারিতার স্বাক্ষর নির্দিষ্ট করে। তাই:

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 (* 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 এর প্রতিটি তাত্ক্ষণিক সংস্করণ ইতিমধ্যে উপলব্ধ একটি নির্দিষ্ট ফাংশন দিয়ে ইনস্ট্যান্ট করা হয়। সুতরাং আমরা do_op এর কোডটি "রিটার্ন এ + বি" এর মতো দেখতে অনেকটা প্রত্যাশা করি। (লিস্প প্রোগ্রামারগণ, আপনার স্মার্টিং বন্ধ করুন!)

আমরা এটিও নিশ্চিত করতে পারি যে এটি আমরা যা চাই তার নিকটেই কারণ এটি:

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

সংকলন করতে ব্যর্থ হবে। জিসিসি বলেছেন: "ত্রুটি: 'ফানক_পিটার' একটি ধ্রুবক-প্রকাশে উপস্থিত হতে পারে না other অন্য কথায়, আমি সম্পূর্ণভাবে 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);

যে উদাহরণ কাজ করবে! (আমি এটি ভাল সি ++ এর পরামর্শ দিচ্ছি না তবে ...) যা ঘটেছে তা হল ড_পোপ বিভিন্ন ফাংশনের স্বাক্ষরগুলির চারপাশে উল্টানো হয়েছে এবং প্রতিটি পৃথক ইনস্ট্যান্টেশন বিভিন্ন ধরণের জবরদস্তি কোড লিখবে। সুতরাং 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);যা "স্পষ্টভাবে অন্তর্ভুক্ত হচ্ছে না" is
ড্যান নিসেনবাউম

এটিকে অন্তর্ভুক্ত করার উদাহরণের জন্য এখানে দেখুন: স্ট্যাকওভারফ্লো / প্রশ্নগুলি / 60৮০7762//২ মনে হচ্ছে যে সংকলকরা আজকাল বেশ স্মার্ট হয়ে উঠছে।
বিগস্যান্ডউইচ

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নিকটতমটি হ'ল দুবার সংজ্ঞা দিতে পারেন - একবার নন-টাইপ প্যারামিটার দিয়ে এবং একবার টাইপ প্যারামিটার দিয়ে with

// 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 এবং ভিজ্যুয়াল স্টুডিও 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;
আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.