Пространства имён
Варианты
Действия

std::visit

Материал из cppreference.com
 
 
Библиотека утилит
Языковая поддержка
Поддержка типов (базовые типы, RTTI)
Макросы тестирования функциональности библиотеки (C++20)    
Управление динамической памятью
Программные утилиты
Поддержка сопрограмм (C++20)
Вариативные функции
Трёхстороннее сравнение (C++20)
(C++20)
(C++20)(C++20)(C++20)(C++20)(C++20)(C++20)
Общие утилиты
Дата и время
Функциональные объекты
Библиотека форматирования (C++20)
(C++11)
Операторы отношения (устарело в C++20)
Целочисленные функции сравнения
(C++20)(C++20)(C++20)    
(C++20)
Операции обмена и типа
(C++14)
(C++11)
(C++11)
(C++11)
(C++17)
Общие лексические типы
(C++11)
(C++17)
(C++17)
(C++17)
(C++11)
(C++17)
(C++23)
Элементарные преобразования строк
(C++17)
(C++17)
 
 
<tbody> </tbody>
Определено в заголовочном файле <variant>
template <class Visitor, class... Variants> constexpr /* смотрите ниже */ visit( Visitor&& vis, Variants&&... vars );
(1) (начиная с C++17)
template <class R, class Visitor, class... Variants> constexpr R visit( Visitor&& vis, Variants&&... vars );
(2) (начиная с C++20)
template<class... Ts> auto&& as-variant( std::variant<Ts...>& var );
(3) (только для пояснения*)
template<class... Ts> auto&& as-variant( const std::variant<Ts...>& var );
(4) (только для пояснения*)
template<class... Ts> auto&& as-variant( std::variant<Ts...>&& var );
(5) (только для пояснения*)
template<class... Ts> auto&& as-variant( const std::variant<Ts...>&& var );
(6) (только для пояснения*)

Применяет посетителя vis (Callable, который можно вызывать с любой комбинацией типов из variant'ов) к variant'ам vars.

Учитывая VariantBases как decltype(as-variant(std::forward<Variants>(vars))... (набор типов sizeof...(Variants)):

1) Вызывает vis как будто с помощью INVOKE(std::forward<Visitor>(vis),
std::get<indices>(std::forward<VariantBases>(vars))...)
, где indices равно as-variant(vars).index()....
2) Вызывает vis как будто с помощью INVOKE<R>(std::forward<Visitor>(vis),
std::get<indices>(std::forward<VariantBases>(vars))...)
, где indices равно as-variant(vars).index()....

Эти перегрузки участвуют в разрешении перегрузки, только если каждый тип в VariantBases является допустимым типом. Если выражение, обозначенное как INVOKE или INVOKE<R> (начиная с C++20) недействительно, или результаты INVOKE или INVOKE<R> (начиная с C++20) имеют разные типы или категории значений для разных indices, программа некорректная.

3-6) Шаблоны функций as-variant, предназначенные только для описания, принимают значение, тип которого может быть выведен для std::variant<Ts...> (то есть либо std::variant<Ts...>, либо тип, производный от std::variant<Ts...>), и возвращают значение std::variant с той же const-квалификацией и категорией значений.
3,4) Возвращает var.
5,6) Возвращает std::move(var).

Параметры

vis Callable, который принимает все возможные альтернативы из каждого variant
vars список variant'ов для передачи посетителю

Возвращаемое значение

1) Результат операции INVOKE. Тип возвращаемого значения это тип, полученный путём применения decltype к результату.
2) Ничего, если R равно (возможно cv-квалифицированному) void; иначе результат операции INVOKE<R>.
3-6) Значение std::variant, преобразованное из var.

Исключения

Генерирует std::bad_variant_access, если as-variant(vars_i).valueless_by_exception() равно true для любого variant vars_i в vars.

Сложность

Когда количество variant'ов равно нулю или одному, вызов вызываемого объекта осуществляется за константное время, т.е. не зависит от того, сколько типов может быть сохранено в variant.

Если количество variant'ов больше одного, вызов вызываемого объекта не требует сложности.

Примечание

Пусть n будет (1 * ... * std::variant_size_v<std::remove_reference_t<VariantBases>>), реализации обычно генерируют таблицу, эквивалентную (возможно, многомерному) массиву указателей на функции n для каждой специализации std::visit,что аналогично реализации виртуальных функций.

Реализации также могут генерировать оператор switch с n ветвями для std::visit (например, реализация MSVC STL использует оператор switch, когда n не больше 256).

В типичных реализациях временная сложность вызова vis может считаться равной сложности доступа к элементу в (возможно, многомерном) массиве или выполнению оператора switch.

Макрос тест функциональности Значение Стандарт Комментарий
__cpp_lib_variant 202102L (C++17) std::visit для классов, производных от std::variant

Пример

#include <iomanip>
#include <iostream>
#include <string>
#include <type_traits>
#include <variant>
#include <vector>

// variant для visit
using var_t = std::variant<int, long, double, std::string>;

// вспомогательная константа для посетителя #3
template<class>
inline constexpr bool always_false_v = false;

// вспомогательный тип для посетителя #4
template<class... Ts>
struct overloaded : Ts... { using Ts::operator()...; };
// явные правила вывода (не требуется, начиная с C++20)
template<class... Ts>
overloaded(Ts...) -> overloaded<Ts...>;

int main()
{
    std::vector<var_t> vec = {10, 15l, 1.5, "привет"};
    
    for (auto& v: vec)
    {
        // 1. посетитель void, вызываемый только для побочных эффектов
        // (здесь для ввода/вывода)
        std::visit([](auto&& arg){ std::cout << arg; }, v);
        
        // 2. посетитель, возвращающий значение, демонстрирует идиому
        // возврата другого variant
        var_t w = std::visit([](auto&& arg) -> var_t { return arg + arg; }, v);
        
        // 3. посетитель, соответствующий типу: лямбда, которая обрабатывает
        // каждый тип по-разному
        std::cout << ". После удвоения variant хранит ";
        std::visit([](auto&& arg)
        {
            using T = std::decay_t<decltype(arg)>;
            if constexpr (std::is_same_v<T, int>)
                std::cout << "int с значением " << arg << '\n';
            else if constexpr (std::is_same_v<T, long>)
                std::cout << "long с значением " << arg << '\n';
            else if constexpr (std::is_same_v<T, double>)
                std::cout << "double с значением " << arg << '\n';
            else if constexpr (std::is_same_v<T, std::string>)
                std::cout << "std::string с значением " << std::quoted(arg) << '\n';
            else 
                static_assert(always_false_v<T>, "неисчерпывающий посетитель!");
        }, w);
    }
    
    for (auto& v: vec)
    {
        // 4. другой посетитель с сопоставлением типов: класс с 3-мя
        // перегруженными operator()
        // Примечание: Шаблон `(auto arg)` operator() будет привязан к `int` и `long`
        //             в этом случае, но в его отсутствие `(double arg)` operator()
        //             *также* будет привязан к `int` и `long`, потому что оба неявно
        //             преобразуются в double. При использовании этой формы
        //             необходимо следить за тем, чтобы неявные преобразования
        //             обрабатывались правильно.
        std::visit(overloaded{
            [](auto arg) { std::cout << arg << ' '; },
            [](double arg) { std::cout << std::fixed << arg << ' '; },
            [](const std::string& arg) { std::cout << std::quoted(arg) << ' '; }
        }, v);
    }
}

Вывод:

10. После удвоения variant хранит int с значением 20
15. После удвоения variant хранит long с значением 30
1.5. После удвоения variant хранит double с значением 3
привет. После удвоения variant хранит std::string с значением "приветпривет"
10 15 1.500000 "привет"

Отчёты о дефектах

Следующие изменения поведения были применены с обратной силой к ранее опубликованным стандартам C++:

Номер Применён Поведение в стандарте Корректное поведение
LWG 2970 C++17 возвращаемый тип перегрузки (1) не сохранял категорию
значения результата операции INVOKE
сохраняет
LWG 3052 C++17 эффекты не были указаны, если какой-либо тип в Variants не являлся std::variant указаны

Смотрите также

обменивает содержимое с другим variant
(public функция-элемент) [править]