विषय पर बढ़ें

अंतिम अद्यतन:2026-10-12

C++ वैरिएडिक टेम्प्लेट्स

《C++那些事》 - वैरिएडिक टेम्प्लेट्स

हाल में Arrow का सोर्स कोड पढ़ते समय एक काफ़ी दिलचस्प डिज़ाइन देखा, जिसमें एक plan में कई नोड्स जोड़े जा सकते हैं, जैसे: proj, agg आदि।

आज इसी हिस्से के डिज़ाइन और क्रियान्वयन की चर्चा करते हैं।

1. नोड जोड़ना

एक plan में प्रायः ढेर सारे नोड्स जोड़ने की ज़रूरत होती है, यहाँ यह काम EmplaceNode को कॉल करके किया जाता है।

return plan->EmplaceNode<ScalarAggregateNode>(
    plan, std::move(inputs), schema(std::move(fields)), std::move(target_field_ids),
    std::move(aggregates), std::move(kernels), std::move(states),
    std::move(owned_options));

इसी तरह, यदि एक proj नोड जोड़ना हो तो ऐसे:

return plan->EmplaceNode<ProjectNode>(plan, std::move(inputs),
                schema(std::move(fields)), std::move(exprs),
                project_options.async_mode);

तो आख़िर कौन सी खूबी है जिससे कोड इतना सहज लिखा जा सकता है?

यहाँ Plan को सरल रूप से लिख देते हैं:

class ARROW_EXPORT ExecPlan
{
    public:
    template <typename Node, typename... Args>
      Node* EmplaceNode(Args&&... args) {
        std::unique_ptr<Node> node{new Node{std::forward<Args>(args)...}};
        auto out = node.get();
        AddNode(std::move(node));
        return out;
      }
 }

देखने में यह सिंटैक्स बहुत जटिल लगता है, और सच है, यह C++11 के बाद का वैरिएडिक टेम्प्लेट है, यहाँ इस कोड का अर्थ समझाया गया है, यानी पैरामीटर पैक को संबंधित Node के कंस्ट्रक्टर को सौंप दिया जाता है। यह उदाहरण थोड़ा जटिल लग सकता है, आगे हम Demo पर चलते हैं।

2.Demo

आगे हमने एक ग्राफ़ बनाया, ग्राफ़ में दो भिन्न नोड्स हैं, क्रमशः नोड A और नोड B, सरलता के लिए यहाँ एक फ़ंक्शन टेम्प्लेट EmplaceNode का उपयोग किया गया है।

NodeA* nodeA = EmplaceNode<NodeA>("Node A", 10, 3.14);
NodeB* nodeB = EmplaceNode<NodeB>(1, "Node B");

पूरा कोड इस प्रकार है:

#include <iostream>
#include <vector>
#include <memory>

class NodeA {
public:
    NodeA(const std::string& name, int value, double weight)
        : name_(name), value_(value), weight_(weight) {}

    const std::string& GetName() const {
        return name_;
    }

private:
    std::string name_;
    int value_;
    double weight_;
};

class NodeB {
public:
    NodeB(int id, const std::string& desc)
        : id_(id), desc_(desc) {}

    int GetID() const {
        return id_;
    }

private:
    int id_;
    std::string desc_;
};
template<typename Node, typename... Args>
Node* EmplaceNode(Args&&... args) {
    std::unique_ptr<Node> node{new Node{std::forward<Args>(args)...}};
    auto out = node.get();
    // AddNode(std::move(node));
    return out;
}

int main() {
    NodeA* nodeA = EmplaceNode<NodeA>("Node A", 10, 3.14);
    NodeB* nodeB = EmplaceNode<NodeB>(1, "Node B");
    std::cout << "Node A name: " << nodeA->GetName() << std::endl;
    std::cout << "Node B ID: " << nodeB->GetID() << std::endl;
    return 0;
}

3. विस्तार - एक ऐसा print फ़ंक्शन लागू करना जो किसी भी प्रकार का डेटा प्रिंट कर सके

सरल लागू संस्करण:

वैरिएडिक टेम्प्लेट पैरामीटर + रिकर्शन के तरीके से लागू किया जा सकता है।

हर बार इसे एक + एक ढेर में तोड़ा जाता है, जब तक केवल एक बचे, इसलिए एक फ़ंक्शन एक को प्रोसेस करने के लिए और दूसरा फ़ंक्शन एक + ढेर को प्रोसेस करने के लिए, जो क्रमशः पहले print_impl और दूसरे print_impl से मेल खाते हैं।

#include <iostream>
#include <utility>

template <typename T>
void print_impl(T&& t) {
    std::cout << t;
}

template <typename T, typename... Args>
void print_impl(T&& t, Args&&... args) {
    std::cout << t;
    print_impl(std::forward<Args>(args)...);
}

template <typename... Args>
void print(Args&&... args) {
    print_impl(std::forward<Args>(args)...);
}

int main() {
    int x = 1;
    const char* str = "Hello, world!";
    print("The value of x is ", x, ", and the string is ", str, ".\n");
    return 0;
}

संबंधित विषय

टिप्पणियाँ