Std क्यों नहीं :: समारोह अधिभार संकल्प में भाग लेते हैं?


17

मुझे पता है कि निम्नलिखित कोड संकलन नहीं करेगा।

void baz(int i) { }
void baz() {  }


class Bar
{
    std::function<void()> bazFn;
public:
    Bar(std::function<void()> fun = baz) : bazFn(fun){}

};

int main(int argc, char **argv)
{
    Bar b;
    return 0;
}

क्योंकि std::functionकहा जाता है कि ओवरलोड रिज़ॉल्यूशन पर विचार न करें, जैसा कि मैंने इस अन्य पोस्ट में पढ़ा है ।

मैं इस तरह के समाधान को मजबूर करने वाली तकनीकी सीमाओं को पूरी तरह से नहीं समझता हूं।

मैं के बारे में पढ़ा अनुवाद के चरणों और टेम्पलेट्स cppreference पर है, लेकिन मैं किसी भी तर्क की सोच भी नहीं सकता कि मैं की एक प्रति नहीं मिल सका। एक आधे आम आदमी को समझाया (अभी भी C ++ में नया), क्या और किस चरण के अनुवाद के दौरान उपरोक्त संकलन करने में विफल रहता है?


1
@ ईवीजी लेकिन केवल एक अधिभार है जो ओपी उदाहरण में स्वीकार किए जाने के लिए समझ में आता है। आपके उदाहरण में दोनों एक मैच
बनायेंगे

जवाबों:


13

इसका वास्तव में "अनुवाद के चरणों" से कोई लेना-देना नहीं है। यह विशुद्ध रूप से के निर्माणकर्ताओं के बारे में है std::function

देखें, std::function<R(Args)>की आवश्यकता नहीं है को देखते हुए समारोह है कि वास्तव में प्रकार के R(Args)। विशेष रूप से, इसके लिए यह आवश्यक नहीं है कि इसे फ़ंक्शन पॉइंटर दिया जाए। यह किसी भी प्रतिदेय प्रकार (सदस्य समारोह सूचक, किसी वस्तु की एक अधिभार है कि ले जा सकते हैं operator()जब तक यह invokable है के रूप में) के रूप में अगर यह लग गए Argsमापदंडों और रिटर्न कुछ के लिए परिवर्तनीय R (या अगर Rहै void, यह कुछ भी लौट सकते हैं)।

ऐसा करने के लिए, की उचित निर्माता std::functionचाहिए एक हो टेम्पलेट : template<typename F> function(F f);। यही है, यह किसी भी फ़ंक्शन प्रकार (उपरोक्त प्रतिबंधों के अधीन) ले सकता है।

अभिव्यक्ति bazएक अधिभार सेट का प्रतिनिधित्व करती है। यदि आप ओवरलोड सेट को कॉल करने के लिए उस अभिव्यक्ति का उपयोग करते हैं, तो यह ठीक है। यदि आप किसी विशिष्ट फ़ंक्शन पॉइंटर को लेने वाले फ़ंक्शन के लिए एक पैरामीटर के रूप में उस अभिव्यक्ति का उपयोग करते हैं, तो C ++ एकल कॉल पर सेट किए गए अधिभार को विघटित कर सकता है, इस प्रकार यह ठीक बना सकता है।

हालाँकि, एक बार एक फंक्शन एक टेम्प्लेट होता है, और आप यह जानने के लिए टेम्प्लेट तर्क तर्क का उपयोग कर रहे हैं कि वह पैरामीटर क्या है, C ++ में अब यह निर्धारित करने की क्षमता नहीं है कि अधिभार सेट में सही अधिभार क्या है। इसलिए आपको इसे सीधे निर्दिष्ट करना होगा।


मुझे क्षमा करें यदि मुझे कुछ गलत मिला है, लेकिन कैसे आओ C ++ में उपलब्ध अधिभार के बीच अंतर करने की कोई क्षमता नहीं है? अब मैं देख रहा हूं कि std :: function <T> किसी भी संगत प्रकार को स्वीकार करता है, न केवल एक सटीक मिलान, बल्कि उन दो बाजों () ओवरलोड्स में से केवल एक ही अदृश्य है, जैसे कि यह निर्दिष्ट पैरामीटर लिया गया है। यह असंभव क्यों है?
तुअराइज डेस

क्योंकि सभी C ++ देखता है कि मैंने हस्ताक्षर किया है। यह नहीं पता कि यह किसके खिलाफ मैच होना चाहिए। अनिवार्य रूप से, किसी भी समय आप किसी भी चीज के खिलाफ एक अधिभार सेट का उपयोग करते हैं, यह स्पष्ट नहीं है कि सी ++ कोड (कोड टेम्पलेट होने की घोषणा) से स्पष्ट रूप से सही उत्तर क्या है , भाषा आपको मजबूर करती है कि आप क्या मतलब है।
निकोल बोलस

1
@TuRtoise: functionवर्ग टेम्पलेट पर टेम्पलेट पैरामीटर अप्रासंगिक है । आपके द्वारा कॉल किए जा रहे कंस्ट्रक्टर पर टेम्पलेट पैरामीटर क्या मायने रखता है । जो सिर्फ है typename F: उर्फ, किसी भी प्रकार।
निकोल बोलस

1
@TuRtoise: मुझे लगता है कि आप कुछ गलत समझ रहे हैं। यह "सिर्फ एफ" है क्योंकि यह है कि कैसे टेम्पलेट्स काम करते हैं। हस्ताक्षर से, वह फ़ंक्शन कंस्ट्रक्टर किसी भी प्रकार का लेता है, इसलिए टेम्पलेट तर्क कटौती के साथ इसे कॉल करने का कोई भी प्रयास पैरामीटर से प्रकार को घटा देगा। ओवरलोड सेट में कटौती लागू करने का कोई भी प्रयास एक संकलित त्रुटि है। कंपाइलर सेट से हर संभव प्रकार को हटाने की कोशिश नहीं करता है, यह देखने के लिए कि कौन से काम करते हैं।
निकोल बोलस

1
"ओवरलोड सेट में कटौती लागू करने का कोई भी प्रयास एक संकलित त्रुटि है। कंपाइलर सेट से हर संभव प्रकार को हटाने की कोशिश नहीं करता है जो यह देखने के लिए कि कौन से काम करते हैं।" वास्तव में मैं क्या याद कर रहा था, धन्यवाद :)
तुअराइज डेस 13'19

7

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

न ही यहां हो रहा है।

std::functionऐसी कोई भी वस्तु लेता है जो उसके हस्ताक्षर के अनुकूल हो। यह विशेष रूप से एक फ़ंक्शन पॉइंटर नहीं लेता है। (एक लैम्ब्डा एक std फ़ंक्शन नहीं है, और एक std फ़ंक्शन लैम्बडा नहीं है)

अब मेरे होमब्रेव फंक्शन वेरिएंट में, हस्ताक्षर के लिए R(Args...)मैं भी R(*)(Args...)इस कारण से एक तर्क (सटीक मिलान) स्वीकार करता हूं । लेकिन इसका मतलब है कि यह "संगत" हस्ताक्षरों के ऊपर "सटीक मिलान" हस्ताक्षर को बढ़ाता है।

मुख्य समस्या यह है कि एक अधिभार सेट C ++ ऑब्जेक्ट नहीं है। आप एक अधिभार सेट को नाम दे सकते हैं, लेकिन आप इसे "मूल" के आसपास पारित नहीं कर सकते।

अब, आप इस तरह से एक फ़ंक्शन का छद्म अधिभार सेट बना सकते हैं:

#define RETURNS(...) \
  noexcept(noexcept(__VA_ARGS__)) \
  -> decltype(__VA_ARGS__) \
  { return __VA_ARGS__; }

#define OVERLOADS_OF(...) \
  [](auto&&...args) \
  RETURNS( __VA_ARGS__(decltype(args)(args)...) )

यह एकल C ++ ऑब्जेक्ट बनाता है जो फ़ंक्शन नाम पर अधिभार रिज़ॉल्यूशन कर सकता है।

मैक्रोज़ का विस्तार करते हुए, हमें यह मिलता है:

[](auto&&...args)
noexcept(noexcept( baz(decltype(args)(args)...) ) )
-> decltype( baz(decltype(args)(args)...) )
{ return baz(decltype(args)(args)...); }

जो लिखने में कष्टप्रद है। एक सरल, केवल थोड़ा कम उपयोगी, संस्करण यहाँ है:

[](auto&&...args)->decltype(auto)
{ return baz(decltype(args)(args)...); }

हमारे पास एक लैम्ब्डा है जो किसी भी प्रकार के तर्कों को लेता है, फिर उन्हें आगे की ओर ले जाता है baz

फिर:

class Bar {
  std::function<void()> bazFn;
public:
  Bar(std::function<void()> fun = OVERLOADS_OF(baz)) : bazFn(fun){}
};

काम करता है। हम ओवरलोड रिज़ॉल्यूशन को लैंबडा में रखते हैं fun, जिसे हम funसीधे स्टोर करते हैं , बजाय एक ओवरलोड सेट को सीधे पास करने के (जो इसे हल नहीं कर सकता है)।

C ++ भाषा में एक ऑपरेशन को परिभाषित करने के लिए कम से कम एक प्रस्ताव है जो एक फ़ंक्शन नाम को ओवरलोड सेट ऑब्जेक्ट में परिवर्तित करता है। जब तक ऐसा मानक प्रस्ताव मानक में नहीं होता है, तब तक OVERLOADS_OFमैक्रो उपयोगी है।

आप एक कदम आगे जा सकते हैं, और कास्ट-से-संगत-फ़ंक्शन-पॉइंटर का समर्थन कर सकते हैं।

struct baz_overloads {
  template<class...Ts>
  auto operator()(Ts&&...ts)const
  RETURNS( baz(std::forward<Ts>(ts)...) );

  template<class R, class...Args>
  using fptr = R(*)(Args...);
  //TODO: SFINAE-friendly support
  template<class R, class...Ts>
  operator fptr<R,Ts...>() const {
    return [](Ts...ts)->R { return baz(std::forward<Ts>(ts)...); };
  }
};

लेकिन उस पर आपत्ति होने लगी है।

जीवंत उदाहरण

#define OVERLOADS_T(...) \
  struct { \
    template<class...Ts> \
    auto operator()(Ts&&...ts)const \
    RETURNS( __VA_ARGS__(std::forward<Ts>(ts)...) ); \
\
    template<class R, class...Args> \
    using fptr = R(*)(Args...); \
\
    template<class R, class...Ts> \
    operator fptr<R,Ts...>() const { \
      return [](Ts...ts)->R { return __VA_ARGS__(std::forward<Ts>(ts)...); }; \
    } \
  }

5

यहाँ मुद्दा यह है कि संकलक को सूचक क्षय करने के लिए फ़ंक्शन करने के लिए कुछ भी नहीं बता रहा है। यदि आपके पास है

void baz(int i) { }
void baz() {  }

class Bar
{
    void (*bazFn)();
public:
    Bar(void(*fun)() = baz) : bazFn(fun){}

};

int main(int argc, char **argv)
{
    Bar b;
    return 0;
}

तब कोड काम करेगा क्योंकि अब कंपाइलर को पता है कि आपको कौन सा फंक्शन चाहिए क्योंकि आप जिस तरह का ठोस काम कर रहे हैं।

जब आप इसका उपयोग करते std::functionहैं, तो यह फंक्शन ऑब्जेक्ट कंस्ट्रक्टर होता है जिसका स्वरूप होता है

template< class F >
function( F f );

और चूंकि यह एक टेम्प्लेट है, इसलिए इसे पास की गई वस्तु के प्रकार को घटाने की आवश्यकता है। चूंकि bazएक अतिभारित फ़ंक्शन है, इसलिए कोई एकल प्रकार नहीं है जिसे कटौती की जा सकती है ताकि टेम्पलेट कटौती विफल हो जाए और आपको एक त्रुटि मिल जाए। आपको उपयोग करना होगा

Bar(std::function<void()> fun = (void(*)())baz) : bazFn(fun){}

एक प्रकार से बल प्राप्त करना और कटौती की अनुमति देना।


"चूँकि baz एक अतिभारित फ़ंक्शन है, इसलिए कोई एकल प्रकार नहीं है जिसे घटाया जा सकता है" लेकिन C ++ 14 के बाद से "यह निर्माता ओवरलोड रिज़ॉल्यूशन में भाग नहीं लेता है जब तक कि एफ तर्क प्रकारों के लिए कॉल करने योग्य नहीं है ... और वापसी प्रकार R" मैं हूं यकीन नहीं है, लेकिन मैं यह उम्मीद करने के लिए करीब था कि यह अस्पष्टता को हल करने के लिए पर्याप्त होगा
idclev 463035818

3
@ पूर्व-परिचित_463035818 लेकिन यह निर्धारित करने के लिए, उसे पहले एक प्रकार की कटौती करने की आवश्यकता है, और यह तब से नहीं हो सकता जब तक कि यह एक अतिभारित नाम न हो।
नाथनऑलिवर

1

इस बिंदु पर कंपाइलर तय कर रहा है कि किस ओवरलोड को std::functionकंस्ट्रक्टर में पास करना है, यह सभी जानते हैं कि std::functionकिसी भी प्रकार को लेने के लिए कंस्ट्रक्टर को टेम्पर्ड किया गया है। यह दोनों अधिभार की कोशिश करने की क्षमता नहीं रखता है और पाता है कि पहला एक संकलन नहीं करता है लेकिन दूसरा करता है।

इसे हल करने का तरीका स्पष्ट रूप से संकलक को बताना है जो आपके साथ अधिभार चाहता है static_cast:

Bar(std::function<void()> fun = static_cast<void(*)()>(baz)) : bazFn(fun){}
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.