В этом посте мы рассмотрели использование std::visit
в C++, мощной утилиты, позволяющей применять различные функции к вариантному объекту в зависимости от его текущего типа. std::visit
может быть полезен в сценариях, где нам нужно обрабатывать варианты объектов с несколькими возможными типами, например, в синтаксическом анализаторе, где нам может потребоваться по-разному обрабатывать различные типы входных данных. Чтобы понять, чего достигает std::visit
, мы рассмотрели альтернативный способ достижения той же функциональности, используя операторы if-else и перегрузку функций.
Простой пример
Вот простой типичный пример перебора массива вариантов 3 разных типов и вызова функций, перегруженных для каждого типа:
#include <iostream> #include <variant> #include <vector> void func(int i) { std::cout << "Called func(int): " << i << std::endl; } void func(double d) { std::cout << "Called func(double): " << d << std::endl; } void func(const std::string& s) { std::cout << "Called func(string): " << s << std::endl; } int main() { std::vector<std::variant<int, double, std::string>> myVector = {1, 3.14, "Hello"}; for (auto& element : myVector) { std::visit([](auto&& arg){ func(arg); }, element); } return 0; }
В этом примере мы определяем три перегруженные функции: func(int)
, func(double)
и func(const std::string&)
. Затем мы создаем вектор std::variant<int, double, std::string>
и инициализируем его тремя элементами разных типов.
В цикле for мы используем std::visit
для вызова соответствующей перегрузки функции для каждого элемента в векторе. std::visit
принимает вызываемый объект в качестве своего первого аргумента, который в данном случае является лямбда-функцией, которая вызывает func
со своим аргументом. Синтаксис auto&&
в лямбда-функции говорит компилятору вывести тип аргумента и создать для него ссылку для пересылки, что позволяет нам вызывать func
с правильной перегрузкой для каждого типа варианта.
std::visit
— это полиморфная функция времени выполнения, которая определяет тип объекта варианта во время выполнения, а затем вызывает соответствующую перегрузку функции.
Когда мы вызываем std::visit
, мы передаем ему вызываемый объект, который определяет набор перегруженных функциональных объектов для каждого типа в варианте. Во время выполнения std::visit
использует тип объекта варианта, чтобы определить, какой объект функции вызывать, а затем передает объект варианта этому объекту функции.
Таким образом, в приведенном мной примере лямбда-функция, переданная std::visit
, является объектом посетителя, который определяет набор перегруженных func
функций для каждого типа в варианте. Во время выполнения std::visit
определяет тип объекта варианта в векторе, а затем вызывает соответствующую функцию func
для этого типа.
Как std::visit
узнает тип объекта варианта?
std::visit
использует информацию о типе, хранящуюся в самом объекте варианта, для определения его типа во время выполнения. Когда мы создаем вариантный объект, он содержит значение одного из своих альтернативных типов вместе с дискриминатором, указывающим, какой тип он имеет в настоящее время.
Вариантный объект хранит этот дискриминатор внутри, поэтому, когда мы вызываем std::visit
для варианта объект, он проверяет этот дискриминатор, чтобы определить тип объекта.
Как только std::visit
узнает тип объекта, он выбирает соответствующий функциональный объект для вызова из набора перегруженных функциональных объектов, предоставленных посетителем. Затем он вызывает этот функциональный объект, передавая вариантный объект в качестве аргумента.
Итак, в нашем примере, когда мы вызываем std::visit
для каждого элемента в векторе, он проверяет дискриминатор вариантного объекта, чтобы определить его тип, а затем вызывает соответствующую функцию func
для этого типа.
Этот эквивалентный код без использования перегрузок посещения и функции может объяснить, что std::visit
делает концептуально.
#include <iostream> #include <vector> #include <variant> void func(int i) { std::cout << "Called func(int): " << i << std::endl; } void func(double d) { std::cout << "Called func(double): " << d << std::endl; } void func(const std::string& s) { std::cout << "Called func(string): " << s << std::endl; } int main() { std::vector<std::variant<int, double, std::string>> vec = {1, 3.14, "hello"}; for (const auto& variant : vec) { if (std::holds_alternative<int>(variant)) { int value = std::get<int>(variant); func(value); } else if (std::holds_alternative<double>(variant)) { double value = std::get<double>(variant); func(value); } else if (std::holds_alternative<std::string>(variant)) { std::string value = std::get<std::string>(variant); func(value); } } return 0; }
В этом примере мы определяем три перегруженные функции: func(int)
, func(double)
и func(const std::string&)
.
Затем мы создаем вектор вариантов объектов vec
, который содержит по одному элементу каждого типа: int
, double
и std::string
.
Затем мы перебираем каждый элемент в vec
, используя std::holds_alternative
для определения типа текущего варианта объекта. Мы используем std::get
для извлечения значения вариантного объекта, а затем передаем это значение соответствующей функции в зависимости от его типа.
Краткое содержание
Таким образом, мы рассмотрели использование std::visit
в C++, мощной утилиты, позволяющей применять различные функции к вариантному объекту в зависимости от его текущего типа. В целом, сообщение в блоге призвано помочь читателям понять основы std::visit и то, как его можно использовать для безопасного обхода вариантных объектов в C++.
Повышение уровня кодирования
Спасибо, что являетесь частью нашего сообщества! Перед тем, как ты уйдешь:
- 👏 Хлопайте за историю и подписывайтесь на автора 👉
- 📰 Смотрите больше контента в публикации Level Up Coding
- 💰 Бесплатный курс собеседования по программированию ⇒ Просмотреть курс
- 🔔 Подписывайтесь на нас: Twitter | ЛинкедИн | "Новостная рассылка"
🚀👉 Присоединяйтесь к коллективу талантов Level Up и найдите прекрасную работу