C ++ में टाइप इंफॉर्मेशन बैकवर्ड कब आती है?


92

मैंने स्टीफन टी। लवविज CppCon 2018को "क्लास टेम्प्लेट आर्ग्यूमेंट डिडक्शन " पर बात करते हुए देखा , जहां कुछ बिंदु पर वह कहते हैं:

C ++ प्रकार की जानकारी में लगभग कभी पीछे की तरफ नहीं बहती है ... मुझे "लगभग" कहना पड़ा क्योंकि एक या दो मामले हैं, संभवतः अधिक लेकिन बहुत कम

यह जानने की कोशिश करने के बावजूद कि वह किन मामलों का हवाला दे रहा है, मैं कुछ भी नहीं कर सकता। इसलिए सवाल:

जिन मामलों में C ++ 17 मानक जनादेश देते हैं, वे प्रकार की जानकारी पीछे की ओर फैलती हैं?


आंशिक विशेषज्ञता और विनाशकारी कार्य से मेल खाते पैटर्न।
v.oddou

जवाबों:


80

यहाँ कम से कम एक मामला है:

struct foo {
  template<class T>
  operator T() const {
    std::cout << sizeof(T) << "\n";
    return {};
  }
};

यदि आप करते हैं foo f; int x = f; double y = f;, तो यह पता लगाने के लिए कि "क्या Tहै " टाइप करें "बैकवर्ड" प्रवाहित होगा operator T

आप इसे और अधिक उन्नत तरीके से उपयोग कर सकते हैं:

template<class T>
struct tag_t {using type=T;};

template<class F>
struct deduce_return_t {
  F f;
  template<class T>
  operator T()&&{ return std::forward<F>(f)(tag_t<T>{}); }
};
template<class F>
deduce_return_t(F&&)->deduce_return_t<F>;

template<class...Args>
auto construct_from( Args&&... args ) {
  return deduce_return_t{ [&](auto ret){
    using R=typename decltype(ret)::type;
    return R{ std::forward<Args>(args)... };
  }};
}

तो अब मैं कर सकता हूँ

std::vector<int> v = construct_from( 1, 2, 3 );

और यह काम करता है।

बेशक, सिर्फ क्यों नहीं {1,2,3}? खैर, {1,2,3}एक अभिव्यक्ति नहीं है।

std::vector<std::vector<int>> v;
v.emplace_back( construct_from(1,2,3) );

जो, माना जाता है, थोड़ा और अधिक आवश्यक है: लाइव उदाहरण । (मुझे कटौती की वापसी एफ की एक SFINAE जांच करनी है, तो F को SFINAE के अनुकूल बनाएं, और मुझे std :: initializer_list in deduce_return_t ऑपरेटर T. में ब्लॉक करना होगा)


बहुत ही दिलचस्प जवाब है, और मैंने एक नई चाल सीखी है इसलिए बहुत-बहुत धन्यवाद! मुझे आपके उदाहरण को संकलित करने के लिए एक टेम्पलेट कटौती दिशानिर्देश जोड़ना था , लेकिन इसके अलावा यह एक आकर्षण की तरह काम करता है!
मैसिमिलियानो

5
&&पर क्वालीफायर operator T()एक महान स्पर्श है; autoयदि autoइसका दुरुपयोग किया जाता है, तो यह संकलन त्रुटि के साथ खराब बातचीत से बचने में मदद करता है।
जस्टिन

1
यह बहुत प्रभावशाली है, क्या आप मुझे उदाहरण में विचार करने के लिए कुछ संदर्भ / बात कर सकते हैं? या शायद यह मूल है :) ...
llllllllll

3
@ लिली कौन सा विचार? मैं 5 की गणना करता हूं: रिटर्न प्रकार को कम करने के लिए ऑपरेटर टी का उपयोग करना एक लंबोदर को काटे गए प्रकार को पारित करने के लिए टैग का उपयोग करना? रूपांतरण ऑपरेटर का उपयोग करके अपने-अपने प्लेसमेंट ऑब्जेक्ट निर्माण को रोल करें? सभी 4 से जुड़ रहे हैं?
यक्क - एडम नेवरामोंट

1
@ लिली था "अधिक उन्नत तरीका" उदाहरण है, जैसा कि मैंने कहा, सिर्फ 4 या तो विचारों को एक साथ चिपका दिया। मैंने इस पोस्ट के लिए मक्खी पर gluing किया था, लेकिन मैंने निश्चित रूप से कई जोड़े या यहां तक ​​कि एक साथ इस्तेमाल किए गए लोगों के तीनों को देखा है। यह यथोचित अस्पष्ट तकनीकों का एक समूह है (जैसा कि टोत्सी शिकायत करता है), लेकिन उपन्यास कुछ भी नहीं।
यक - एडम नेवरामॉन्ट

31

Stephan T. Lavavej ने एक ट्वीट में जिस मामले के बारे में बात कर रहे थे उसे समझाया :

मैं जिस मामले के बारे में सोच रहा था वह यह है कि आप एक अतिभारित / टेम्प्लेटेड फ़ंक्शन का पता कैसे ले सकते हैं और यदि इसका उपयोग किसी विशिष्ट प्रकार के एक वैरिएबल को शुरू करने के लिए किया जा रहा है, तो यह वह नापसंद होगा जो आप चाहते हैं। (वहाँ क्या क्या है की एक सूची है)

हम ओवरलोड फ़ंक्शन के पते पर cppreference पेज से इसके उदाहरण देख सकते हैं , मेरे पास कुछ नीचे दिए गए हैं:

int f(int) { return 1; } 
int f(double) { return 2; }   

void g( int(&f1)(int), int(*f2)(double) ) {}

int main(){
    g(f, f); // selects int f(int) for the 1st argument
             // and int f(double) for the second

     auto foo = []() -> int (*)(int) {
        return f; // selects int f(int)
    }; 

    auto p = static_cast<int(*)(int)>(f); // selects int f(int)
}

माइकल पार्क कहते हैं :

यह एक ठोस प्रकार को शुरू करने तक सीमित नहीं है। यह सिर्फ तर्कों की संख्या से भी पता लगा सकता है

और यह जीवंत उदाहरण प्रदान करता है :

void overload(int, int) {}
void overload(int, int, int) {}

template <typename T1, typename T2,
          typename A1, typename A2>
void f(void (*)(T1, T2), A1&&, A2&&) {}

template <typename T1, typename T2, typename T3,
          typename A1, typename A2, typename A3>
void f(void (*)(T1, T2, T3), A1&&, A2&&, A3&&) {}

int main () {
  f(&overload, 1, 2);
}

जिसे मैं यहाँ थोड़ा और विस्तृत करता हूँ ।


4
हम इसका भी वर्णन कर सकते हैं: ऐसे मामले जहां अभिव्यक्ति का प्रकार संदर्भ पर निर्भर करता है?
MM

20

मेरा मानना ​​है कि अतिभारित कार्यों की स्थिर ढलाई में प्रवाह विपरीत दिशा में जाता है जैसा कि सामान्य अधिभार संकल्प में होता है। तो उनमें से एक पीछे की तरफ है, मुझे लगता है।


7
मेरा मानना ​​है कि यह सही है। और यह तब है जब आप एक फ़ंक्शन पॉइंटर प्रकार के लिए एक फ़ंक्शन नाम पास करते हैं; प्रकार की जानकारी अभिव्यक्ति के संदर्भ से बहती है (जिस प्रकार आप / निर्माण / आदि को असाइन कर रहे हैं) उस फ़ंक्शन के नाम में पीछे की तरफ यह निर्धारित करने के लिए कि कौन सा अधिभार चुना गया है।
यक्क - एडम नेवरामोंट 22
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.