c++ सी++ में और डी में मेटाप्रोग्रामिंग




metaprogramming d (8)

डी मेटाप्रोग्रामिंग के सर्वोत्तम उदाहरण डी मानक लाइब्रेरी मॉड्यूल हैं जो इसके बनाम सी ++ बूस्ट और एसटीएल मॉड्यूल का भारी उपयोग करते हैं। डी की std.range , std.algorithm , std.functional और std.parallelism । इनमें से कोई भी सी ++ में कार्यान्वित करना आसान नहीं होगा, कम से कम स्वच्छ, अभिव्यक्तिपूर्ण एपीआई के साथ जो डी मॉड्यूल के पास है।

डी मेटाप्रोग्रामिंग, आईएमएचओ सीखने का सबसे अच्छा तरीका इस तरह के उदाहरणों से है। मैंने std.algorithm और std.range को कोड पढ़कर काफी हद तक सीखा, जो आंद्रेई अलेक्जेंड्रेस्कु द्वारा लिखे गए थे (एक सी ++ टेम्पलेट मेटाप्रोग्रामिंग गुरु जो डी के साथ भारी रूप से शामिल हो गया है)। मैंने तब जो कुछ सीखा और मैंने std.parallelism मॉड्यूल का योगदान किया।

यह भी ध्यान रखें कि डी ने समय फ़ंक्शन मूल्यांकन (सीटीएफई) संकलित किया है जो सी ++ 1x के constexpr के constexpr लेकिन constexpr अधिक सामान्य है कि रनटाइम पर मूल्यांकन किए जा सकने वाले कार्यों के बड़े और बढ़ते सबसेट का मूल्यांकन संकलन समय पर अनमोडिफाइड किया जा सकता है। यह संकलन-समय कोड जनरेशन के लिए उपयोगी है, और जेनरेट कोड को स्ट्रिंग मिश्रित का उपयोग करके संकलित किया जा सकता है।

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

मुझे डी के साथ कोई अनुभव नहीं है, लेकिन मैं उत्सुक हूं, आप डी में क्या कर सकते हैं और आप टेम्पलेट मेटाप्रोग्रामिंग की बात करते समय सी ++ में नहीं कर सकते हैं?


डी में टेम्पलेट मेटाप्रोग्रामिंग की मदद करने वाली दो सबसे बड़ी चीजें टेम्पलेट की बाधाएं और static if - जिनमें से दोनों सी ++ सैद्धांतिक रूप से जोड़ सकते हैं और इससे इसका लाभ होगा।

टेम्पलेट की बाधाएं आपको टेम्पलेट पर एक शर्त लगाने की अनुमति देती हैं जो टेम्पलेट के लिए तत्काल होने में सक्षम होना चाहिए। उदाहरण के लिए, यह std.algorithm.find के std.algorithm.find से एक का हस्ताक्षर है:

R find(alias pred = "a == b", R, E)(R haystack, E needle)
    if (isInputRange!R &&
        is(typeof(binaryFun!pred(haystack.front, needle)) : bool))

इस टेम्पलेटेड फ़ंक्शन को तत्काल करने में सक्षम होने के लिए, टाइप R एक इनपुट रेंज होना चाहिए जैसा कि std.range.isInputRange द्वारा परिभाषित किया std.range.isInputRange (इसलिए isInputRange!R true होना चाहिए), और दिए गए भविष्य को एक बाइनरी फ़ंक्शन होना चाहिए दिए गए तर्कों के साथ संकलित करता है और एक प्रकार देता है जो bool लिए पूरी तरह से परिवर्तनीय है। यदि टेम्पलेट बाधा में स्थिति का परिणाम false , तो टेम्पलेट संकलित नहीं होगा। न केवल यह उन गैर-टेम्पलेट त्रुटियों से आपकी रक्षा करता है जो आपको C ++ में प्राप्त होते हैं जब टेम्पलेट उनके दिए गए तर्कों के साथ संकलित नहीं होंगे, लेकिन यह ऐसा करता है ताकि आप टेम्पलेट की बाधाओं के आधार पर टेम्पलेट को अधिभारित कर सकें। उदाहरण के लिए, खोज का एक और अधिभार है जो है

R1 find(alias pred = "a == b", R1, R2)(R1 haystack, R2 needle)
if (isForwardRange!R1 && isForwardRange!R2
        && is(typeof(binaryFun!pred(haystack.front, needle.front)) : bool)
        && !isRandomAccessRange!R1)

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

स्थिति में और भी सुधार static if । टेम्पलेट बाधाओं की तरह ही, संकलन समय पर मूल्यांकन की जा सकने वाली किसी भी शर्त का उपयोग इसके साथ किया जा सकता है। जैसे

static if(isIntegral!T)
{
    //...
}
else static if(isFloatingPoint!T)
{
    //...
}
else static if(isSomeString!T)
{
    //...
}
else static if(isDynamicArray!T)
{
    //...
}
else
{
    //...
}

किस शाखा को संकलित किया गया है इस पर निर्भर करता है कि किस शर्त पर पहले true मूल्यांकन किया जाता true । तो, एक टेम्पलेट के भीतर, आप अपने कार्यान्वयन के टुकड़ों को उन प्रकारों के आधार पर विशेषज्ञ कर सकते हैं जिनके साथ टेम्पलेट को तत्काल बनाया गया था - या किसी अन्य चीज के आधार पर जिसे संकलित समय पर मूल्यांकन किया जा सकता है। उदाहरण के लिए, core.time उपयोग करता है

static if(is(typeof(clock_gettime)))

सिस्टम को clock_gettime प्रदान करता है या नहीं (यदि clock_gettime है, तो इसका उपयोग करता है, अन्यथा यह clock_gettime का उपयोग करता है) के आधार पर अलग-अलग कोड संकलित करने के लिए।

शायद सबसे कठिन उदाहरण मैंने देखा है कि डी टेम्पलेट्स पर सुधार करता है, एक समस्या के साथ है जो काम पर मेरी टीम सी ++ में भाग गई। हमें एक टेम्पलेट को अलग-अलग करने के लिए आवश्यक था कि यह किस प्रकार दिया गया था, किसी विशेष बेस क्लास से लिया गया था या नहीं। हम इस स्टैक ओवरफ्लो प्रश्न के आधार पर समाधान का उपयोग कर समाप्त हो गए। यह काम करता है, लेकिन यह जांचने के लिए काफी जटिल है कि एक प्रकार दूसरे से प्राप्त होता है या नहीं।

डी में, हालांकि, आपको बस इतना करना है : ऑपरेटर का उपयोग करें। जैसे

auto func(T : U)(T val) {...}

यदि T पूरी तरह से U परिवर्तनीय है (जैसा कि T होगा यदि T U से लिया गया था), तो func संकलित होगा, जबकि यदि T पूरी तरह से U परिवर्तनीय नहीं है, तो यह नहीं होगा। यह सरल सुधार बुनियादी टेम्पलेट विशेषज्ञता को और अधिक शक्तिशाली बनाता है (यहां तक ​​कि टेम्पलेट की बाधाओं या static if )।

निजी तौर पर, मैं शायद ही कभी कंटेनरों के अलावा सी ++ में टेम्पलेट्स का उपयोग करता हूं और <algorithm> में कभी-कभी फ़ंक्शन का उपयोग करता हूं, क्योंकि वे उपयोग करने के लिए बहुत अधिक दर्द होते हैं। वे बदसूरत त्रुटियों के परिणामस्वरूप और कुछ भी फैंसी करने के लिए बहुत मुश्किल हैं। कुछ भी जटिल करने के लिए, आपको टेम्पलेट्स और टेम्पलेट मेटाप्रोग्रामिंग के साथ बहुत कुशल होने की आवश्यकता है। हालांकि डी में टेम्पलेट्स के साथ, यह इतना आसान है कि मैं उन्हें हर समय उपयोग करता हूं। त्रुटियों को समझना और निपटना बहुत आसान होता है (हालांकि वे त्रुटियों से भी बदतर हैं, आमतौर पर गैर-टेम्पलेट किए गए कार्यों के साथ होते हैं), और मुझे यह समझने की ज़रूरत नहीं है कि भाषा को फैंसी मेटाप्रोग्रामिंग के साथ मैं क्या करना चाहता हूं ।

ऐसा कोई कारण नहीं है कि सी ++ इन क्षमताओं में से अधिकतर लाभ नहीं प्राप्त कर सकता है कि डी (सी ++ अवधारणाओं को मदद मिलेगी यदि वे कभी भी उन्हें हल कर लेते हैं), लेकिन जब तक वे टेम्पलेट बाधाओं के समान संरचनाओं के साथ बुनियादी सशर्त संकलन जोड़ते हैं और static if सी ++, सी ++ टेम्पलेट्स उपयोग और शक्ति की आसानी के मामले में डी टेम्पलेट्स की तुलना करने में सक्षम नहीं होंगे।


यहां डी कोड का एक टुकड़ा है जो कस्टम-निर्मित map() जो इसके परिणाम संदर्भ द्वारा देता है

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

ध्यान देने योग्य कुछ महत्वपूर्ण विशेषताएं निम्नलिखित हैं:

  • टेम्पलेट्स विविध हैं: map() किसी भी तर्क ले सकता है।

  • कोड (अपेक्षाकृत) छोटा है ! Mapper संरचना, जो मूल तर्क है, केवल 15 लाइनें हैं - और फिर भी यह बहुत कम कर सकती है। मेरा मुद्दा यह नहीं है कि यह सी ++ में असंभव है, लेकिन यह निश्चित रूप से कॉम्पैक्ट और साफ नहीं है।

import std.metastrings, std.typetuple, std.range, std.stdio;

void main() {
    auto arr1 = [1, 10, 5, 6], arr2 = [3, 9, 80, 4];

    foreach (ref m; map!min(arr1, arr2)[1 .. 3])
        m *= 50;

    writeln(arr1, arr2); // Voila! You get:  [1, 10, 250, 6][3, 450, 80, 4]
}

auto ref min(T...)(ref T values) {
    auto p = &values[0];
    foreach (i, v; values)
        if (v < *p)
            p = &values[i];
    return *p;
}

Mapper!(F, T) map(alias F, T...)(T args) { return Mapper!(F, T)(args); }

struct Mapper(alias F, T...) {
    T src;  // It's a tuple!

    @property bool empty() { return src[0].empty; }

    @property auto ref front() {
        immutable sources = FormatIota!(q{src[%s].front}, T.length);
        return mixin(Format!(q{F(%s)}, sources));
    }

    void popFront() { foreach (i, x; src) { src[i].popFront(); } }

    auto opSlice(size_t a, size_t b) {
        immutable sliced = FormatIota!(q{src[%s][a .. b]}, T.length);
        return mixin(Format!(q{map!F(%s)}, sliced));
    }
}


// All this does is go through the numbers [0, len),
// and return string 'f' formatted with each integer, all joined with commas
template FormatIota(string f, int len, int i = 0) {
    static if (i + 1 < len)
        enum FormatIota = Format!(f, i) ~ ", " ~ FormatIota!(f, len, i + 1);
    else
        enum FormatIota = Format!(f, i);
}

स्ट्रिंग मैनिपुलेशन, स्ट्रिंग पार्सिंग भी।

यह एक एमपी लाइब्रेरी है जो बीएनएफ (अधिक या कम) बीएनएफ का उपयोग करके तारों में परिभाषित व्याकरण के आधार पर रिकर्सिव सभ्य पार्सर्स उत्पन्न करती है। मैंने वर्षों में इसे छुआ नहीं है लेकिन यह काम करता था।


मेरा मानना ​​है कि इस रेंडरर की तुलना में डी टेम्पलेट सिस्टम की अविश्वसनीय शक्ति (टीएम) दिखाने के लिए कुछ भी बेहतर नहीं है, मैंने कई साल पहले पाया था:

हाँ! यह वास्तव में संकलक द्वारा उत्पन्न होता है ... यह वास्तव में "प्रोग्राम" है, और काफी रंगीन है।

संपादित करें

स्रोत ऑनलाइन वापस प्रतीत होता है।


अच्छी तरह से डी में आप टेम्पलेट पैरामीटर पर स्थिर बाधाओं को आसानी से लगा सकते हैं और स्थैतिक के साथ वास्तविक टेम्पलेट तर्क के आधार पर कोड लिख सकते हैं।
टेम्पलेट विशेषज्ञता और अन्य चालों का उपयोग करके सी ++ के साथ सरल मामलों के लिए अनुकरण करना संभव है (बूस्ट देखें) लेकिन यह एक पिटा है और बहुत सीमित कारण है कि कंपाइलर प्रकारों के बारे में कई विवरणों का खुलासा नहीं करता है।

एक चीज सी ++ वास्तव में बस नहीं कर सकती परिष्कृत संकलन समय कोड पीढ़ी है।


डी में टेम्पलेट मेटाप्रोग्रामिंग में कुछ चीजें शांत हो सकती हैं जिन्हें आप सी ++ में नहीं कर सकते हैं। सबसे महत्वपूर्ण बात यह है कि आप एक दर्द के बिना टेम्पलेट मेटाप्रोग्रामिंग कर सकते हैं!


डी रे ट्रेसिंग पोस्ट का मुकाबला करने के लिए, यहां एक सी ++ संकलन समय रे ट्रैसर ( metatrace ) है:

(वैसे, यह ज्यादातर सी ++ 2003 मेटाप्रोग्रामिंग का उपयोग करता है; यह नए constexpr साथ और अधिक पठनीय होगा)





d2