Последнее обновление: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. Этот пример может показаться довольно сложным, поэтому перейдём к демо.
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.
#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;
}
Похожие темы¶
- Принцип работы и применение std::move в C++ | C++那些事
- Обзор новых возможностей C++11 | C++那些事
- Сравнение std::bind и lambda в C++ | C++那些事