假设存在一个定义如下的变体 v:
std::variant<int,char,double,bool,std::string> v;
Run Code Online (Sandbox Code Playgroud)
我正在尝试使用 std::visit 或 std::get 从 std::variant 获取基础值。
我试着天真地这样做:
constexpr size_t idx = v.index();
auto k = std::get<idx>(v);
Run Code Online (Sandbox Code Playgroud)
但是后来了解到,如果变体 v 本身不是 constexpr,这将失败。即便如此,使用 std::string 也可能存在问题(由于 std::string 的析构函数的定义)。
我的第二次尝试是尝试执行以下操作:
auto k = std::visit([](auto arg){return arg;}, v);
Run Code Online (Sandbox Code Playgroud)
但是收到了这个:
$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}; }
^
Run Code Online (Sandbox Code Playgroud)
我不明白为什么 std::visit 调用不起作用。我以为我提供了一个简单的 lambda 表达式,它采用变体的所有可能类型并返回基础值,但似乎我误解了一些东西。
我现在想使用 std::variant (在最初考虑 std::any 之后(请参阅避免使用std::any编写相同的重复类型检查代码)但我需要一种方法来返回包含的值。任何帮助都会很大非常感谢。
我正在尝试使用 std::visit 或 std::get 从 std::variant 获取基础值。
如果您想要的确实是持有基础当前值,那么您必须让访问支持对每个可能的特定处理,例如,如下所示:
#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;
}
Run Code Online (Sandbox Code Playgroud)
这是因为C++ 是一种静态类型语言,因为在编译时必须知道所有类型的变量。因此,auto当您想要在运行时期间可能作为当前值保留的各种类型之一std::variant时,编译器不能允许您仅声明并完成它。
...但我需要一种方法来返回包含的值。
作为静态类型,在 C++ 中没有办法在不经过可能的情况下这样做。如果您想要一个接受此类实例std::variant并返回a 的函数std::string,那么您可以修改上面的代码以std::to_string()返回从std::get()(冗余但仅用于说明)返回的每个值。然后,您将在“非表单”中拥有包含的类型std::variant。
摘自评论:
那么为什么 std::visit([](auto&& arg){std::cout << arg;}, v); 工作?
这是有效的,因为您没有尝试将基础类型的变量分配/复制到您自己的变量中。这同样需要在编译期间知道此类变量的类型。但是,当std::variant被要求是提供一个字符串表示的它目前持有价值-例如,由于operator <<的std::cout-那么在内部它的作用是相同的语义我们的if-else开关的上方,即不同的处理对每个可能的基础类型的这variant实例。
澄清:显然有不止一种方法可以指定处理std::variant实例当前可能持有的不同可能性。例如,如std::visitcppreference 页面所示,您可以使用基于模板推导指南的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);
Run Code Online (Sandbox Code Playgroud)
您的行没有编译,因为 lambda 需要通过-> your_variant显式声明它的返回类型,因为编译器无法从 lambda 推断它。
解决同一问题的另一种有效语法只是声明参数类型,因此编译器可以知道它返回的是什么,就像它是一个返回的函数一样auto:
auto k2 = std::visit([](your_variant arg) {return arg;}, v);
Run Code Online (Sandbox Code Playgroud)
这样做的编译问题:
constexpr size_t idx = v.index();
auto k = std::get<idx>(v);
Run Code Online (Sandbox Code Playgroud)
再次,由于静态类型,即v可以保持在运行时其指数的任何单独一个,和模板参数为std::get()需要在编译时是已知的。
您尝试执行的操作无法工作,因为对象的类型variant在运行时是已知的,并且在编译时必须知道要存储它的变量的类型。
处理这个问题的模式variant是在一个可以处理任何类型的模板函数上做工作,或者有一组可以接受来自变体的任何类型的重载。
在模板函数上完成所有工作:
std::visit([] (const auto& k) { std::cout << k; }, v);
Run Code Online (Sandbox Code Playgroud)
或者,在函数内部用 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);
Run Code Online (Sandbox Code Playgroud)
调用不同的重载
template <class... Fs> struct Overload : Fs... { using Fs::operator()...; };
template <class... Fs> Overload(Fs...) -> Overload<Fs...>;
Run Code Online (Sandbox Code Playgroud)
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
);
Run Code Online (Sandbox Code Playgroud)