Получить значение варианта, который сам может быть другим вариантом

У меня есть вариант ScalarVar

using ScalarVar = std::variant<int, std::string>;

И вариант Var, который сам может быть ScalarVar или std::vector из ScalarVars

using Var = std::variant<ScalarVar, std::vector<ScalarVar>>;

Я хочу создать функцию 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;
}

Это должно просто вернуть T, если T не является std::variant, и вернуть std::get<InnerT>, если T является вариантом std ::, содержащим тип 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> > >&)}

Как я могу добиться требуемого поведения?


person Blue7    schedule 27.02.2020    source источник
comment
Непонятно, откуда в вашем коде возникла эта ошибка.   -  person pablo285    schedule 27.02.2020
comment
Ладно я отредактирую вопрос   -  person Blue7    schedule 27.02.2020
comment
Что такое typeof?   -  person Timo    schedule 27.02.2020
comment
@Timo Расширение GCC, эквивалентное decltype, IIRC.   -  person L. F.    schedule 27.02.2020
comment
Скажем, у вас есть два скаляра ScalarVar i = 0; ScalarVar j = 1; и вектор std::vector<ScalarVar> vec{i, j}. Что должно Get<int>(vec) вернуть? Первое совпадающее значение, например, 0?   -  person pschill    schedule 27.02.2020
comment
@pschill vec не является std::variant, поэтому это ошибка компилятора.   -  person Timo    schedule 27.02.2020


Ответы (2)


Проблема в этой строке:

std::visit([](const auto& i){ return i; }, var);

Вызов этого прямо из main с помощью 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
    );
}
person pschill    schedule 27.02.2020

Вы должны различать T, std::variant<...> и все остальное, когда вы вызываете std::visit.

template<typename T>
struct Getter {
    T operator()(const T & t) { return t; }

    template <typename ... Ts>
    T operator()(const std::variant<Ts...> & var) { return std::visit(*this, var); }

    template <typename U>
    T operator()(U) { throw std::bad_variant_access(); }
};

template<typename T, typename Variant>
T Get(const Variant& var) {
    Getter<T> getter;
    return getter(var);
}

/* or 
template <typename T>
inline Getter<T> Get;
*/
person Caleth    schedule 27.02.2020