2016-08-24 3 views
8

मेरे पास एक टेम्पलेट में कुछ कष्टप्रद कोड है जो @R. Martinho Fernandes की चाल का उपयोग करता है जो लूप को एक भिन्न टेम्पलेट में कुछ पैक पैरामीटर अनलोल करता है और तर्क सूची में प्रत्येक तर्क पर एक ही कोड का आह्वान करता है।सी ++ लैम्ब्डा टेम्प्लेट में दूसरे विस्तार पर वैरिएबल कैप्चर नहीं कर रहा है?

हालांकि, यह लगता है जैसे कि लैम्ब्डा को ठीक से प्रारंभ नहीं किया जा रहा है और वे इसके बजाय फ़ैक्टर (?) उदाहरणों में चर साझा कर रहे हैं, जो गलत लगता है।

इस कोड को देखते हुए:

#include <iostream> 
#include <functional> 

template<typename... Args> 
void foo(Args ... args) { 
    int * bar = new int(); 
    *bar = 42; 

    using expand_type = int[]; 
    expand_type{(
    args([bar]() { 
     std::cerr<<std::hex; 
     std::cerr<<"&bar="<<(void*)&bar<<std::endl; 
     std::cerr<<" bar="<<(void*)bar<<std::endl; 
     std::cerr<<" bar="<<*bar<<std::endl<<std::endl; 
    }), 
    0) ... 
    }; 
}; 

int main() { 
    std::function<void(std::function<void()>)> clbk_func_invoker = [](std::function<void()> f) { f(); }; 
    foo(clbk_func_invoker, clbk_func_invoker); 

    return 0; 
} 

मैं निम्नलिखित उत्पादन प्राप्त करें:

&bar=0x7ffd22a2b5b0 
    bar=0x971c20 
    bar=2a 

&bar=0x7ffd22a2b5b0 
    bar=0 
Segmentation fault (core dumped) 

तो, मेरा मानना ​​है कि मैं देख रहा हूँ कि दो functor उदाहरणों पर कब्जा कर लिया चर के लिए एक ही पते का हिस्सा है bar, और पहले मज़ेदार के आवेषण के बाद, bar को nullptr पर सेट किया जा रहा है, और फिर दूसरा मज़ेदार seg'-faults जब को 01 को अव्यवस्थित करने की कोशिश करता हैपरिवर्तनीय (ठीक उसी पते में)।

एफवाईआई, मुझे एहसास है कि मैं [bar](){... फ़ैक्टर को एक चर std::function चर में ले जाकर और फिर उस चर को कैप्चर करके इस समस्या के आसपास काम कर सकता हूं। हालांकि, मैं क्यों समझना चाहूंगा दूसरा मज़ेदार उदाहरण सटीक उसी bar पते का उपयोग कर रहा है और इसे nullptr मान क्यों प्राप्त हो रहा है।

मैंने जीएनयू के जी ++ के साथ अपने ट्रंक संस्करण को पुनर्प्राप्त और कल संकलित किया।

उत्तर

2

पैरामीटर में लैम्बडास के साथ पैक संकलक फिट बैठते हैं।इससे बचने का एक तरीका विस्तार भाग और लैम्ब्डा भाग को अलग करना है।

template<class F, class...Args> 
auto for_each_arg(F&& f) { 
    return [f=std::forward<F>(f)](auto&&...args){ 
    using expand_type = int[]; 
    (void)expand_type{0,(void(
     f(decltype(args)(args)) 
    ),0)...}; 
    }; 
} 

यह एक लैम्ब्डा f लेता है और एक उद्देश्य यह है कि अपने तर्कों में से प्रत्येक पर f लागू करेगा देता है।

हम तो पुनर्लेखन कर सकते हैं foo इसका इस्तेमाल करने की:

template<typename... Args> 
void foo(Args ... args) { 
    int * bar = new int(); 
    *bar = 42; 

    for_each_arg([bar](auto&& f){ 
    f([bar]() { 
     std::cerr<<std::hex; 
     std::cerr<<"&bar="<<(void*)&bar<<std::endl; 
     std::cerr<<" bar="<<(void*)bar<<std::endl; 
     std::cerr<<" bar="<<*bar<<std::endl<<std::endl; 
    }); 
    }) 
    (std::forward<Args>(args)...); 
} 

live example

मैंने शुरू में सोचा कि इसे std::function कन्स्ट्रक्टर के साथ करना था। ऐसा नहीं होता। A simpler example एक std::function कि उसी तरह दुर्घटनाओं के बिना:

template<std::size_t...Is> 
void foo(std::index_sequence<Is...>) { 
    int * bar = new int(); 
    *bar = 42; 

    using expand_type = int[]; 
    expand_type{(
    ([bar]() { 
     std::cerr<<"bar="<<*bar<<'\n'; 
    })(), 
    (int)Is) ... 
    }; 
} 

int main() { 
    foo(std::make_index_sequence<2>{}); 

    return 0; 
} 

we can invoke the segfault without the cerr, हमें disassembly आसान है कि पढ़ने के लिए दे रही है:

void foo<3, 0ul, 1ul>(std::integer_sequence<unsigned long, 0ul, 1ul>)::{lambda()#1}::operator()() const: 
    pushq %rbp 
    movq %rsp, %rbp 
    movq %rdi, -8(%rbp) 
    movq -8(%rbp), %rax 
    movq (%rax), %rax 
    movl $3, (%rax) 
    nop 
    popq %rbp 
    ret 
void foo<3, 0ul, 1ul>(std::integer_sequence<unsigned long, 0ul, 1ul>): 
    pushq %rbp 
    movq %rsp, %rbp 
    pushq %rbx 
    subq $40, %rsp 
    movl $4, %edi 
    call operator new(unsigned long) 
    movl $0, (%rax) 
    movq %rax, -24(%rbp) 
    movq -24(%rbp), %rax 
    movl $42, (%rax) 
    movq -24(%rbp), %rax 
    movq %rax, -48(%rbp) 
    leaq -48(%rbp), %rax 
    movq %rax, %rdi 
    call void foo<3, 0ul, 1ul>(std::integer_sequence<unsigned long, 0ul, 1ul>)::{lambda()#1}::operator()() const 
    movabsq $-4294967296, %rax 
    andq %rbx, %rax 
    movq %rax, %rbx 
    movq $0, -32(%rbp) 
    leaq -32(%rbp), %rax 
    movq %rax, %rdi 
    call void foo<3, 0ul, 1ul>(std::integer_sequence<unsigned long, 0ul, 1ul>)::{lambda()#1}::operator()() const 
    movl %ebx, %edx 
    movabsq $4294967296, %rax 
    orq  %rdx, %rax 
    movq %rax, %rbx 
    nop 
    addq $40, %rsp 
    popq %rbx 
    popq %rbp 
    ret 

मैं अभी तक disassembly पार्स करने के लिए है, लेकिन यह स्पष्ट रूप से के राज्य ट्रैश पहली बार खेलते समय दूसरा लैम्ब्डा।

+0

व्यक्तिगत रूप से, मुझे लगता है कि यह वास्तव में जीसीसी में एक कंपाइलर बग है, लेकिन मैं इसे दर्ज करने से पहले बिल्कुल निश्चित होना चाहता हूं। मुझे पता है कि आपको लगभग कंपाइलर को कभी दोष नहीं देना चाहिए, लेकिन शायद इस बार ... इसके अलावा, मेरे असली मामले के लिए मेरे पास एक काम है। मैं आश्चर्यचकित नहीं होगा अगर यह संकलक के लिए अपेक्षाकृत अद्वितीय परीक्षण मामला था।कोड बल्कि preosterous है। –

+0

@Yakk आपने मूल मुद्दे का उत्तर नहीं दिया, * .. वे बदले में फ़ैक्टर (?) उदाहरणों में चर साझा कर रहे हैं .. * क्यों पैक का विस्तार करना केवल एक प्रति साझा करने के बजाय प्रत्येक विस्तार के लिए बार की एक प्रति नहीं बनाते सभी विस्तारित बंद होने के लिए बार का, इस तरह का विस्तार इतना खास क्यों बनाता है? – xhamr

+1

@xhamr क्योंकि पैरामीटर पैक विस्तार के अंदर लैम्बडास के लिए समर्थन flakey है? मैंने इसे छोड़ दिया क्योंकि अधिकांश कंपाइलर कानूनी कोड संकलित करने में विफल रहे। – Yakk

2

सबसे पहले मेरे पास कोई समाधान नहीं है, मैं इस अतिरिक्त जानकारी को टिप्पणी के रूप में जोड़ना चाहता हूं, लेकिन दुर्भाग्य से मैं अभी तक टिप्पणी नहीं कर सकता।

मैं इंटेल 17 सी ++ संकलक के साथ अपने पिछले कोड की कोशिश की और ठीक काम किया:

&bar=0x7fff29e40c50 
    bar=0x616c20 
    bar=2a 

&bar=0x7fff29e40c50 
    bar=0x616c20 
    bar=2a 

कुछ मामलों में &bar (नई कब्जा कर लिया मूल्य की दुकान के लिए इस्तेमाल किया चर का पता) पहली कॉल के बीच अलग था और दूसरा, लेकिन यह भी काम किया।

मैं भी जीएनयू के ग्राम ++ int को int* से bar का प्रकार बदलने के साथ अपने कोड की कोशिश की। पर कब्जा कर लिया मूल्य भी इस मामले में दूसरे और लगातार कॉल में गलत था:

&bar=0x7fffeae12480 
    bar=2a 
&bar=0x7fffeae12480 
    bar=0 
&bar=0x7fffeae12480 
    bar=0 

अंत में मैं थोड़ा कोड को संशोधित करने और मूल्य और वस्तु से गुजर करने की कोशिश की है, तो प्रति निर्माता बुलाया जाना चाहिए:

#include <iostream> 
#include <functional> 

struct A { 
    A(int x) : _x(x) { 
     std::cerr << "Constructor!" << n++ << std::endl; 
    } 
    A(const A& a) : _x(a._x) { 
     std::cerr << "Copy Constructor!" << n++ << std::endl; 
    } 
    static int n; 
    int _x; 
}; 

int A::n = 0; 

template<typename... Args> 
void foo(Args ... args) { 
    A a(42); 

    std::cerr << "-------------------------------------------------" << std::endl; 
    using expand_type = int[]; 
    expand_type { 
    (args([a]() { 
      std::cerr << "&a, "<< &a << ", a._x," << a._x << std::endl; 
     } 
     ), 
    0) ... 
    }; 
std::cerr << "-------------------------------------------------" << std::endl; 
} 

int main() { 
    std::function<void(std::function<void()>)> clbk_func_invoker = [](std::function<void()> f) { f(); }; 
    foo(clbk_func_invoker, clbk_func_invoker, clbk_func_invoker); 
    return 0; 
} 

जी ++ (g++ (GCC) 6.1.0) का मेरा वर्तमान संस्करण इस कोड को संकलित करने में सक्षम नहीं है। मैं भी इंटेल के साथ की कोशिश की और यह काम किया, हालांकि मैं पूरी तरह से समझ में नहीं आता क्यों प्रतिलिपि निर्माता कई बार कहा जाता है:

Constructor!0 
------------------------------------------------- 
Copy Constructor!1 
Copy Constructor!2 
Copy Constructor!3 
&a, 0x617c20, a._x,42 
Copy Constructor!4 
Copy Constructor!5 
Copy Constructor!6 
&a, 0x617c20, a._x,42 
Copy Constructor!7 
Copy Constructor!8 
Copy Constructor!9 
&a, 0x617c20, a._x,42 
------------------------------------------------- 

सभी मैं अब तक का परीक्षण किया है यही कारण है कि।

1

कुछ परीक्षणों के बाद मुझे पता चला कि सभी लैम्बडा के मूल्यांकन के बारे में थे, न कि पैक विस्तार।

आपके पास लैम्बडास का एक सेट है जो पैक विस्तार पूरा होने तक निष्पादित नहीं होता है ताकि निष्पादन के पल में वे सभी चर के समान उदाहरण को देख सकें, जो प्रत्येक लैम्ब्डा के निष्पादन के बाद अलग होगा विस्तार के आदेश के साथ, तो प्रत्येक विस्तार यह चर के स्वयं की प्रतिलिपि है मिलेगा और लैम्ब्डा एक materialized prvalue जो जीवन समाप्त हो गया है पर विचार किया जाएगा:

template<typename... Args> 
void foo(Args ... args) { 
    int * bar = new int(); 
    *bar = 42; 

    using expand_type = int[]; 
    expand_type{(args([bar]{ 
     std::cerr<<std::hex; 
     std::cerr<<"&bar="<<(void*)&bar<<std::endl; 
     std::cerr<<" bar="<<(void*)bar<<std::endl; 
     std::cerr<<" bar="<<*bar<<std::endl<<std::endl; 
     return 0; 
    }()),0) ... 
    }; 
}; 

int main() { 
    std::function<void(int)> clbk_func_invoker = [](int) { };  
    foo(clbk_func_invoker, clbk_func_invoker);  
    return 0; 
} 

live example.

हालांकि, संकलक में सक्षम है जब भी ev अनुकूलन करने के लिए थोड़ा अनुकूलन करने के लिए capture by copy के तहत trivial classes के लिए विस्तार करते समय अल्युटेड लैम्ब्डा का विस्तार किया जाता है और निष्पादित नहीं किया जाता है।

Let एक और अधिक सरल उदाहरण रखा:

struct A{ }; 

template<class... T> 
auto foo(T... args){ 
    A a; 
    std::cout<< &a << std::endl; 
    using expand = int[]; 

expand{ 0,(args([a] { 
     std::cout << &a << " " << std::endl; return 0; }),void(),0)... 
}; 
} 

foo([](auto i){ i(); }, [](auto i){ i(); }); 

विल उत्पादन हर विस्तार लैम्ब्डा के लिए a का एक ही पते, तब भी जब व्यक्तियों a की नकल की संभावना है। चूंकि capture by copy प्रतिलिपि चर के निरंतर संस्करण का उत्पादन करता है और trivial classes के लिए कोई भी उत्परिवर्तन नहीं किया जा सकता है, इसलिए सभी विस्तारित लैम्बडा के माध्यम से एक ही उदाहरण साझा करने के लिए एक प्रकार का प्रदर्शन होता है (क्योंकि कोई परिवर्तन गारंटी नहीं है)।

लेकिन अगर प्रकार अब एक छोटी सी एक नहीं है, कि अनुकूलन से टूटा हुआ है और अलग अलग प्रतियां प्रत्येक विस्तार लैम्ब्डा के लिए आवश्यक हैं:

struct A{ 
    A() = default; 
    A(const A&){} 
}; 

a के लिए A कारण अलग पते पर यह परिवर्तन उत्पादन में प्रदर्शित ।

+0

तो आप कह रहे हैं कि लैम्बडास कैप्चर तर्कों को "तुच्छ" के रूप में गलत वर्गीकृत किया जाता है जब वे वास्तव में "तुच्छ" नहीं होते हैं? वे चर जो मैं गुजर रहा हूं वह एक सूचक है। मैं उम्मीद करता हूं कि सूचक मूल्य की प्रतिलिपि बनाई जाए और उस सूचक के लिए जितना संभव हो सके "तुच्छ" आइटम के करीब हो। अजीब चीज यह है कि पहले लैम्ब्डा के बाद सूचक को 0 पर सेट किया जाता है। ऐसा लगता है कि कुछ प्रकार के विनाशकारी व्यवहार या संभवतः यहां तक ​​कि भ्रष्टाचार भी ढेर। –

+0

मेरे विशिष्ट मामले में मैं वास्तव में _expect_ 'और bar' प्रत्येक आमंत्रण पर समान होगा क्योंकि लैम्ब्डा ढेर पर है और यह ठीक उसी लैम्ब्डा की दो प्रतियां है। अजीब बात यह है कि सूचक का मूल्य '0x0' में संशोधित किया जा रहा है। –

+0

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

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