Попытка вернуть значение из std :: variable с использованием std :: visit и лямбда-выражения - PullRequest
0 голосов
/ 30 августа 2018

Предположим, существует вариант v, определенный следующим образом:

std::variant<int,char,double,bool,std::string> v;

Я пытаюсь получить базовое значение из std :: варианта, используя std :: visit или std :: get.

Я пытался сделать это наивно:

constexpr size_t idx = v.index();
auto k = std::get<idx>(v);

Но потом выяснилось, что это потерпит неудачу, если вариант v не является сам консекспром. И даже тогда могут возникнуть проблемы с использованием std :: string (из-за определения деструктора для std :: string).

Моя вторая попытка состояла в том, чтобы попытаться сделать следующее:

auto k = std::visit([](auto arg){return arg;}, v);

Но получил это:

$g++ -o main *.cpp --std=c++17
In file included from main.cpp:5:0:
/usr/include/c++/7/variant: In instantiation of ‘static constexpr auto std::__detail::__variant::__gen_vtable_impl<std::__detail::__variant::_Multi_array<_Result_type (*)(_Visitor, _Variants ...)>, std::tuple<_Tail ...>, std::integer_sequence<long unsigned int, __indices ...> >::_S_apply() [with _Result_type = int; _Visitor = main()::<lambda(auto:1)>&&; _Variants = {std::variant<int, char, double, bool, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >&}; long unsigned int ...__indices = {1}]’:
/usr/include/c++/7/variant:663:61:   required from ‘static constexpr void std::__detail::__variant::__gen_vtable_impl<std::__detail::__variant::_Multi_array<_Result_type (*)(_Visitor, _Variants ...), __dimensions ...>, std::tuple<_Variants ...>, std::integer_sequence<long unsigned int, __indices ...> >::_S_apply_single_alt(_Tp&) [with long unsigned int __index = 1; _Tp = std::__detail::__variant::_Multi_array<int (*)(main()::<lambda(auto:1)>&&, std::variant<int, char, double, bool, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >&)>; _Result_type = int; _Visitor = main()::<lambda(auto:1)>&&; long unsigned int ...__dimensions = {5}; _Variants = {std::variant<int, char, double, bool, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >&}; long unsigned int ...__indices = {}]’
/usr/include/c++/7/variant:651:39:   required from ‘constexpr const std::__detail::__variant::_Multi_array<int (*)(main()::<lambda(auto:1)>&&, std::variant<int, char, double, bool, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >&), 5> std::__detail::__variant::__gen_vtable<int, main()::<lambda(auto:1)>&&, std::variant<int, char, double, bool, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >&>::_S_vtable’
/usr/include/c++/7/variant:704:29:   required from ‘struct std::__detail::__variant::__gen_vtable<int, main()::<lambda(auto:1)>&&, std::variant<int, char, double, bool, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >&>’
/usr/include/c++/7/variant:1239:23:   required from ‘constexpr decltype(auto) std::visit(_Visitor&&, _Variants&& ...) [with _Visitor = main()::<lambda(auto:1)>; _Variants = {std::variant<int, char, double, bool, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >&}]’
main.cpp:89:49:   required from here
/usr/include/c++/7/variant:704:49:   in constexpr expansion of ‘std::__detail::__variant::__gen_vtable<int, main()::<lambda(auto:1)>&&, std::variant<int, char, double, bool, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >&>::_S_apply()’
/usr/include/c++/7/variant:701:38:   in constexpr expansion of ‘std::__detail::__variant::__gen_vtable_impl<std::__detail::__variant::_Multi_array<int (*)(main()::<lambda(auto:1)>&&, std::variant<int, char, double, bool, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >&), 5>, std::tuple<std::variant<int, char, double, bool, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >&>, std::integer_sequence<long unsigned int> >::_S_apply()’
/usr/include/c++/7/variant:641:19:   in constexpr expansion of ‘std::__detail::__variant::__gen_vtable_impl<std::__detail::__variant::_Multi_array<int (*)(main()::<lambda(auto:1)>&&, std::variant<int, char, double, bool, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >&), 5>, std::tuple<std::variant<int, char, double, bool, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >&>, std::integer_sequence<long unsigned int> >::_S_apply_all_alts<0, 1, 2, 3, 4>(\xe2\x80\x98result_dec\xe2\x80\x99 not supported by dump_expr#<expression error>, (std::make_index_sequence<5>(), std::make_index_sequence<5>()))’
/usr/include/c++/7/variant:686:43: error: invalid conversion from ‘std::__success_type<char>::type (*)(main()::<lambda(auto:1)>&&, std::variant<int, char, double, bool, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >&) {aka char (*)(main()::<lambda(auto:1)>&&, std::variant<int, char, double, bool, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >&)}’ to ‘int (*)(main()::<lambda(auto:1)>&&, std::variant<int, char, double, bool, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >&)’ [-fpermissive]
       { return _Array_type{&__visit_invoke}; }
                                           ^

Я застрял в связи с тем, почему не работает вызов std :: visit. Я думал, что предоставил тривиальное лямбда-выражение, которое принимает все возможные типы для варианта и возвращает базовое значение, но, похоже, я что-то неправильно понимаю.

Я хочу использовать std :: variable сейчас (после первоначального рассмотрения std :: any (см. избегайте написания того же повторяющегося кода проверки типа с помощью std :: any ), но мне нужен способ возврата Содержимое значение. Любая помощь будет принята с благодарностью. Большое спасибо.

Ответы [ 3 ]

0 голосов
/ 30 августа 2018

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

Шаблон для работы с этим variant должен выполнять работу с шаблонной функцией, которая может работать с любым типом, или иметь набор перегрузок, которые могут принимать любой тип из варианта.

Вариант 1

Все ли работают над шаблонной функцией:

std::visit([] (const auto& k) { std::cout << k; }, v);

Или, внутри функции дифференцировать с constexpr if. Но я не вижу в этом смысла, так как есть лучший альтернативный ИМО с перегрузками (см. Далее):

std::visit([] (const auto& k) {
        using T = std::decay_t<decltype(k)>;
        if constexpr (std::is_same_v<T, int>)
            std::cout << "int with value " << k << '\n';
        else if constexpr (std::is_same_v<T, char>)
            std::cout << "char with value " << k << '\n';
        else if constexpr (std::is_same_v<T, double>)
            std::cout << "double with value " << k << '\n';
        else if constexpr (std::is_same_v<T, std::string>)
            std::cout << "std::string with value " << k << '\n';
    }, v);

Вариант 2

вызов различных перегрузок

template <class... Fs> struct Overload : Fs... { using Fs::operator()...; };
template <class... Fs> Overload(Fs...) -> Overload<Fs...>;
std::visit(
    Overload{
        [] (int k) { /* deal with k here */ },
        [] (char k) { /* deal with k here */ },
        [] (double k) { /* deal with k here */ },
        [] (bool k) { /* deal with k here */ },
        [] (std::string k) { /* deal with k here */ }
    },
    v
);
0 голосов
/ 30 августа 2018

Каждая переменная в данной функции C ++ имеет один фиксированный тип.

auto k = std::visit([](auto arg){return arg;}, v);

здесь вы хотите, чтобы k имел один из нескольких различных типов. C ++ не поддерживает это.

«Но», вы говорите, «почему»:

std::visit([](auto arg){std::cout << arg;}, v);

работа? В лямбде arg принимает много разных типов!

Это связано с тем, что [](auto arg){...} - это не одна функция, а (сокращение) для функции шаблона. Функция шаблона - это не функция, а шаблон для создания функций.

Этот код вызывает создание N различных функций, каждая из которых имеет различный тип для auto arg. Все они составлены. Затем std::visit выбирает один для запуска.

std::variant - это способ хранения нескольких различных типов данных в одной переменной. У него фиксированный тип, но он предоставляет visit, так что вы можете безопасно набирать основные данные.

Теперь все не так плохо. Вы можете просто поместить свой код в лямбду .

Так что вместо:

auto k = std::visit([](auto arg){return arg;}, v);
// code using k

сделать это:

std::visit([](auto k){
  // code using k
}, v);

Если вы хотите вернуть значение , вы должны вернуться в страну std::variant. Предположим, вы хотите вернуть std::vector<T>, где T - это тип в варианте.

template<class...Ts>
using var_of_vec = std::variant< std::vector<Ts>... >;
using my_vector = var_of_vec<int,char,double,bool,std::string>;

my_vector v =std::visit([](auto k)->my_vector{
  std::vector<decltype(k)> vec;
  // code using k
  return vec;
}, v);

в теле лямбды вы используете один вектор, затем вы возвращаете вариант векторов.

0 голосов
/ 30 августа 2018

Я пытаюсь получить базовое значение из std :: варианта, используя std :: visit или std :: get.

Если то, что вы хотите, действительно содержит базовое текущее значение, то вам необходимо, чтобы посещение поддерживало определенную обработку каждого возможного, например, например:

#include <string>
#include <variant>

int main()
{
    using your_variant = std::variant<int,char,double,bool,std::string>;
    your_variant v;

    std::visit([](your_variant&& arg) {
        if (std::holds_alternative<int>(arg))
            auto v_int = std::get<int>(arg);
        else if (std::holds_alternative<char>(arg))
            auto v_chart = std::get<char>(arg);
        else if (std::holds_alternative<double>(arg))
            auto v_double = std::get<double>(arg);
        else if (std::holds_alternative<bool>(arg))
            auto v_bool = std::get<bool>(arg);
        else if (std::holds_alternative<std::string>(arg))
            auto v_str = std::get<std::string>(arg);
        }, v);

    return 0;
}

Это потому, что C ++ является статически типизированным языком , т. Е. Все типы переменных должны быть известны во время компиляции . Таким образом, компилятор не может позволить вам просто объявить auto и покончить с ним, когда вы хотите , что может быть одним из различных типов, которые std::variant может содержать в качестве текущего значения в этот момент в течение время выполнения.

... но мне нужен способ вернуть содержащееся в нем значение.

Будучи статически типизированным, в C ++ нет способа сделать это без прохождения возможных случаев. Если вам нужна функция, которая принимает экземпляр такого std::variant и возвращает, скажем, std::string, то вы можете изменить приведенный выше код так, чтобы он возвращал std::to_string() для каждого значения, возвращаемого из std::get() (избыточно, но только для иллюстрации ). Тогда у вас будет содержащийся тип в «не-1032 * форме».

взято из комментариев:

Тогда почему std :: visit ([] (auto && arg) {std :: cout << arg;}, v); работать? </p>

Это работает, потому что вы не пытаетесь присвоить / скопировать переменную базового типа в вашу собственную переменную. Это, опять же, потребовало бы знания типа такой переменной во время компиляции. Но когда требуется std::variant для предоставления строкового представления его текущего значения - например, из-за operator << из std::cout - тогда внутренне то, что он делает, имеет ту же семантику, что и наш переключатель if - else выше, т. е. обработка по-разному для каждого возможного базового типа этого variant экземпляра.

Пояснение: Очевидно, существует несколько способов указать обработку различных возможностей того, что экземпляр std::variant в настоящее время может содержать. Например, как показано на странице std::visit cppreference , вы можете использовать способ вывода шаблона на основе std::visit(overloaded { ..., что в то время как возможно, для лучшего и более короткого кода требуется более глубокое объяснение, чтобы понять механизм, каким я его вижу, поскольку он включает в себя, среди прочего, наследование от лямбды, и поэтому я решил, что это выходит за рамки объяснения этого ответьте относительно того, как я понимаю задаваемый вопрос. Вы можете прочитать все об этом здесь и здесь . Или просто посмотрите пример кода использования в другом ответе на этот вопрос.


Относительно ошибок компиляции: Это сработает для вас просто отлично, но не достигнет того, что вы хотели:

using your_variant = std::variant<int,char,double,bool,std::string>;
your_variant v;

auto k = std::visit([](auto arg)-> your_variant {return arg;}, v);

Ваши строки не скомпилированы, поскольку лямбда-код должен явно объявить тип возвращаемого значения -> your_variant, поскольку компилятор не может вывести его из лямбды.

Другой допустимый синтаксис для решения той же проблемы - просто объявление типа параметра, чтобы компилятор мог знать, что он возвращает, как если бы это была функция, возвращающая auto:

auto k2 = std::visit([](your_variant arg) {return arg;}, v);

Проблема компиляции с этим:

constexpr size_t idx = v.index();
auto k = std::get<idx>(v);

опять же, из-за статической типизации , что v может содержать любой один из его индексов во время выполнения, и аргумент шаблона для std::get() должен быть известным во время компиляции.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...