尝试使用 std::visit 和 lambda 表达式从 std::variant 返回值

新手上路,请多包涵

假设存在一个变体 v,定义如下:

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

我正在尝试使用 std::visit 或 std::get 从 std::variant 获取基础值。

我尝试天真地这样做:

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

但是后来了解到,如果变体 v 本身不是 constexpr ,这将失败。即便如此,使用 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 调用不起作用。我以为我提供了一个简单的 lambda 表达式,它采用变量的所有可能类型并返回基础值,但似乎我误解了一些东西。

我现在想使用 std::variant (在最初考虑 std::any 之后(请参阅 避免使用 std::any 编写相同的重复类型检查代码),但我需要一种方法来返回包含的值。任何帮助都会很大非常感谢。

原文由 markf78 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 1.8k
2 个回答

我正在尝试使用 std::visit 或 std::get 从 std::variant 获取基础值。

如果您想要的确实是持有基础当前值,那么您必须让访问支持对每个可能的特定处理。例如,像这样:

 std::visit([] (const auto& var) {
    if constexpr (std::is_same_v<std::decay_t<decltype(var)>, int>) {
        // Do something with var of type int
    }
    else if constexpr (std::is_same_v<std::decay_t<decltype(var)>, char>) {
        // Do something with var of type char
    }
    else if constexpr (std::is_same_v<std::decay_t<decltype(var)>, double>) {
        // Do something with var of type double
    }
    else if constexpr (std::is_same_v<std::decay_t<decltype(var)>, bool>) {
        // Do something with var of type bool
    }
    else if constexpr (std::is_same_v<std::decay_t<decltype(var)>, std::string>) {
        // Do something with var of type std::string
    }
}, v);

这是因为 C++ 是一种 静态类型 语言,如在 编译时 必须知道所有类型的变量。因此,编译器不允许您仅声明 auto 并在您想要 std::variant 可能作为当前值的各种类型之一 时使用它在 运行时。

…但我需要一种方法来返回包含的值。

由于是静态类型的,C++ 中没有办法在不经历可能的情况下这样做。如果您想要一个调用 std::variant 的实例并返回例如 std::string ,那么您可以修改上面的代码以返回 std::to_string(var) 为每个上述情况 if / else 语句。

  • 请注意,这使用了 constexpr if 关键字。值得一读,以防不清楚为什么这里需要这样做。需要使用 std::decay_t 模板来确保在 std::is_same_v 模板中比较的类型是基本( 非常量和非引用 限定)类型。

摘自评论:

那为什么 std::visit([](auto&& arg){std::cout << arg;}, v);工作?

这是有效的,因为您没有尝试将基础类型的变量分配/复制到您自己的变量中。同样,这需要在编译期间知道此类变量的类型。但是当 std::variant 被要求提供其当前持有值的 字符串表示- 例如由于 operator <<std::cout 那么它在内部做什么与我们的 if - else 上面的开关具有相同的语义,即对这个 variant 实例的每种可能的底层类型进行不同的处理。

澄清: 显然有不止一种方法可以指定处理 std::variant 实例当前可能持有的不同可能性。例如,如 std::visit cppreference page 所示,您可以使用基于 std::visit(overloaded { ...模板推导指南 这样做,虽然可以说可以使代码更好更短,但它需要一些更深入的解释来理解我看到它的机制,因为它包括从 lambda 继承等等,所以我认为它超出了这个答案的解释范围,关于我如何理解这个问题是问。您可以 在此处此处 阅读所有相关信息。或者更容易在此问题的另一个答案中查看使用代码示例。


关于编译错误:这将为您编译就好,但它没有达到您想要的效果:

 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);

您的行没有编译,因为 lambda 需要通过 -> your_variant 显式声明它的返回类型,因为编译器无法从 lambda 推断它。

解决相同问题的另一种有效语法只是声明参数类型,因此编译器可以知道它返回什么,就好像它是一个返回 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()模板参数 需要在编译时知道。

原文由 Geezer 发布,翻译遵循 CC BY-SA 4.0 许可协议

您尝试执行的操作不起作用,因为对象 variant 的类型在运行时是已知的,并且您要存储它的变量的类型必须在编译时知道。

处理这个 variant 的模式是在一个可以处理任何类型的模板函数上工作,或者有一组可以接受变体中的任何类型的重载。

选项1

在模板函数上完成所有工作:

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

或者,在函数内部用 constexpr if 进行区分。但我不明白这一点的意义,因为有一个更好的替代 imo 重载(见下):

 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
);

原文由 bolov 发布,翻译遵循 CC BY-SA 4.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题