获取一个变体的值,它本身可能是另一个变体

Blu*_*ue7 1 c++ variant algebraic-data-types template-meta-programming c++17

我有一个变种 ScalarVar

using ScalarVar = std::variant<int, std::string>;
Run Code Online (Sandbox Code Playgroud)

和一个变体Var,它本身可能是一个ScalarVar或一个std::vectorScalarVar小号

using Var = std::variant<ScalarVar, std::vector<ScalarVar>>;
Run Code Online (Sandbox Code Playgroud)

我想打一个功能template<typename T, typename Variant> T Get(const Variant& var);是,当给定一个变种,不包括内部变形就只能像std::get<T>,也就是说,它会返回的值T,如果Variant当前包含T,或者如果考虑到包含其他变种的变体,它会递归地获取包含键入直到找到非变体,然后返回。

到目前为止,这是我最好的尝试:

#include <iostream>
#include <variant>
#include <string>
#include <typeindex>
#include <vector>
#include <map>

template<typename T, typename... T2>
struct is_variant { static inline constexpr bool value = false; };

template<typename... T>
struct is_variant<std::variant<T...>> { static inline constexpr bool value = true; };

template<typename T, typename Variant>
T Get(const Variant& var) {
    static_assert (is_variant<Variant>::value == true, "Template parameter Variant must be a std::variant");
    auto inner = std::visit([](const auto& i){ return i; }, var);
    if constexpr(is_variant<typeof(inner)>::value) {
        return Get<T>(inner);
    }
    else return inner;
}

int main()
{
    using ScalarVar = std::variant<int, std::string>;
    using Var = std::variant<ScalarVar, std::vector<ScalarVar>>;

    ScalarVar s = 5;
    std::cout << Get<int>(s) << std::endl;

    return 0;
}
Run Code Online (Sandbox Code Playgroud)

这应该简单地返回Tif Tis not an std::variant,并返回std::get<InnerT>ifT是包含 type 的 std::variant T

但是我从 gcc 得到了一个非常复杂的编译错误:

std::cout << Get<int>(s) << std::endl

/usr/include/c++/9/variant:1005: error: invalid conversion from ‘std::__success_type<std::__cxx11::basic_string<char> >::type (*)(Get(const Variant&) [with T = int; Variant = std::variant<int, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >]::<lambda(const auto:22&)>&&, const std::variant<int, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >&)’ {aka ‘std::__cxx11::basic_string<char> (*)(Get(const Variant&) [with T = int; Variant = std::variant<int, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >]::<lambda(const auto:22&)>&&, const std::variant<int, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >&)’} to ‘int (*)(Get(const Variant&) [with T = int; Variant = std::variant<int, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >]::<lambda(const auto:22&)>&&, const std::variant<int, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >&)’ [-fpermissive]
 1005 |       { return _Array_type{&__visit_invoke}; }
      |                                           ^
      |                                           |
      |                                           std::__success_type<std::__cxx11::basic_string<char> >::type (*)(Get(const Variant&) [with T = int; Variant = std::variant<int, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >]::<lambda(const auto:22&)>&&, const std::variant<int, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >&) {aka std::__cxx11::basic_string<char> (*)(Get(const Variant&) [with T = int; Variant = std::variant<int, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >]::<lambda(const auto:22&)>&&, const std::variant<int, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >&)}
Run Code Online (Sandbox Code Playgroud)

我怎样才能得到我需要的行为?

psc*_*ill 6

问题是这一行:

std::visit([](const auto& i){ return i; }, var);
Run Code Online (Sandbox Code Playgroud)

直接从mainwith调用它ScalarVar var = 5;会产生类似的消息。

让我们看看cppreference关于std::visit(C++17)的返回类型是怎么说的:

返回类型是从返回的表达式中推导出来的,就像 decltype 一样。对于所有变体的替代类型的所有组合,如果上述调用不是相同类型和值类别的有效表达式,则调用格式错误。

这意味着你不能std::visit像上面那样调用,因为它必须根据里面的值返回不同的类型(int/ std::string)。

您可以使用函数重载并为std::variant和 其他类型使用两个不同的函数:

template <typename T, typename U>
T Get(U const& value)
{
    if constexpr (std::is_same_v<T, U>)
    {
        return value;
    }
    // If you get here, none of the nested variants had a value of type T.
    return T();
}

template <typename T, typename... Args>
T Get(std::variant<Args...> const& var)
{
    return std::visit(
        [](auto const& value) { return Get<T>(value); },
        var
    );
}
Run Code Online (Sandbox Code Playgroud)