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

时间:2018-08-30 03:43:16

标签: c++ c++17

假设存在定义如下的变体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之后(请参阅avoid writing the same repetitive type-checking code with std::any),但是我需要一种返回包含值的方法,我们将不胜感激,非常感谢。非常感谢

3 个答案:

答案 0 :(得分:3)

  

我正在尝试使用以下方法从std :: variant获取基础值   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的函数,则可以修改上面的代码以针对{{1 }}(冗余但仅用于说明)。然后,您将以“非std::to_string()格式”包含包含类型

摘自评论:

  

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

之所以起作用,是因为您没有尝试将基础类型的变量分配/复制到您自己的变量中。同样,这将需要在编译期间知道此类变量的类型。但是,当要求std::get()提供其当前保持值的字符串表示形式时(例如,由于std::variant的{​​{1}},则在内部它是什么)确实具有与上述std::variant-operator <<开关相同的语义,即对此std::cout实例的每种可能的基础类型进行不同的处理。

说明:显然,有多种方法可以指定处理if实例当前可能具有的各种可能性。例如,如std::visit cppreference page所示,您可能正在使用基于template deduction guideselse的方式,尽管可以说可以使代码更好,更短,但需要花更多的时间来解释。我了解它的机制,因为它包括从lambda继承的知识,因此我认为就我如何理解所要提出的问题而言,它超出了此答案的解释范围。您可以阅读有关herehere的全部信息。或更容易地在此问题的另一个答案中查看用法代码示例。


关于编译错误:这样可以为您编译,但是并没有达到您想要的效果:

variant

您的行没有编译,因为lambda需要由std::variant明确声明其返回类型,因为编译器无法从lambda进行推断。

解决同一问题的另一种有效语法就是声明参数类型,这样编译器就可以知道返回的内容,就好像它是一个返回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可以在运行时保存其任何一个索引,而{em> template参数用于{ {1}}在编译时需要知道。

答案 1 :(得分:2)

您要执行的操作无法正常工作,因为对象variant的类型在运行时是已知的,而要存储它的变量的类型必须在编译时是已知的。

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

选项1

在模板功能上完成所有工作:

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

或者,函数内部是否用constexpr区分。但我看不到这一点的意义,因为有一个更好的替代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
);

答案 2 :(得分:0)

给定C ++函数中的每个变量都有一个固定的类型。

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

在这里您希望k具有多种不同类型之一。 C ++不支持此功能。

“但是”,您说“为什么”:

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

工作吗?在lambda中,arg具有许多不同的类型!

这是因为[](auto arg){...}不是单个函数,而是模板函数的(简写)。模板函数不是函数,而是创建函数的模板。

该代码导致创建N个不同的函数,每个函数对于auto arg具有不同的 类型。它们都已编译。然后,std::visit选择一个运行。

std::variant是我们如何在一个变量中存储多种不同类型的数据。它具有固定的类型,但是它公开了visit,因此您可以通过类型安全地获取基础数据。

现在情况还不错。您可以将代码放入lambda

所以代替:

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

在lambda主体内,您使用单个向量,然后返回向量的变体。