Ir para o conteúdo

Última atualização:2026-10-12

Templates variádicos em C++

Templates variádicos em 《C++那些事》

Recentemente, ao ler o código-fonte do Arrow, encontrei um design bastante interessante: em um plan é possível adicionar vários nós, por exemplo: proj, agg, etc.

Hoje vamos falar sobre o design e a implementação dessa parte.

1.Adição de nós

Para um plan, normalmente é necessário adicionar um conjunto de nós; isso é feito chamando 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));

Da mesma forma, adicionar um nó proj seria:

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

Então, qual é o encanto que faz esse código ser escrito de forma tão fluida?

Aqui está uma versão simplificada do 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;
      }
 }

Essa sintaxe parece muito complexa, e não é por acaso: trata-se dos templates variádicos introduzidos a partir do C++11. Para explicar o significado desse código: ele repassa o pacote de parâmetros para o construtor do Node correspondente. Este exemplo pode parecer um tanto complexo, então vamos entrar na parte de Demo.

2.Demo

Em seguida, criamos um grafo com dois nós diferentes, o nó A e o nó B; para simplificar, usamos aqui uma função de template EmplaceNode.

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

O código completo é o seguinte:

#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.Extensão - implementar uma função print capaz de imprimir dados de qualquer tipo

Versão de implementação simples:

Basta usar parâmetros de template variádicos + recursão.

A cada passo, divide-se em um + um monte, até restar apenas um; por isso existe uma função que processa um elemento e outra que processa um + um monte, correspondendo respectivamente ao primeiro print_impl e ao segundo 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;
}

Tópicos relacionados

Comentários