Вариант посетителя с разными типами возврата - PullRequest
2 голосов
/ 14 июня 2019

Рассмотрим следующий фрагмент кода, который использует boost::variant (но также должен применяться и к std::variant).

#include <vector>
#include <boost/variant.hpp>

int main()
{
    boost::variant<std::vector<int>, std::vector<double> > vr 
        = std::vector<int>(5, 5);;

    // OK, no problem.
    boost::apply_visitor([](auto a) { std::cout << a[0] << "\n"; }, vr);

    // ERROR: return types must not differ.
    //boost::apply_visitor([](auto a) { return a.begin(); }, vr);
}

Здесь у нас есть вариант, который съедает стандартные векторы разных типов (например, int и double в этом примере), и мы хотели бы иметь посетителя, который возвращает объекты разных типов (в этом случае , итераторы к началу основного контейнера). Однако, это не скомпилируется, поскольку очевидно, что std::vector<int>::iterator - это не то же самое, что std::vector<double>::iterator. Есть ли аккуратный способ добиться этого, возможно, через дополнительный уровень косвенности?

Ответы [ 4 ]

3 голосов
/ 14 июня 2019

Вы можете вернуть другой вариант

#include <iostream>
#include <vector>
#include <boost/variant.hpp>

int main()
{
    boost::variant<std::vector<int>, std::vector<double> > vr 
        = std::vector<int>(5, 5);
    using iter_variant = boost::variant<std::vector<int>::iterator, std::vector<double>::iterator >;

    using value_variant = boost::variant<int, double>;

    // OK, no problem.
    boost::apply_visitor([](auto a) { std::cout << a[0] << "\n"; }, vr);

    // Also OK
    boost::apply_visitor([](auto a) -> iter_variant { return a.begin(); }, vr);

    // Also OK
    boost::apply_visitor([](auto a) -> value_variant { return a[0]; }, vr);
}

Просмотреть его в прямом эфире

Учитывая обобщенную лямбду и вариант, вы можете получить соответствующий тип возврата.

template<typename Func, typename Variant>
struct visitor_result;

template<typename Func, typename ... Ts>
struct visitor_result<Func, boost::variant<Ts...>>
{
    using type = boost::variant<decltype(std::declval<Func>()(std::declval<Ts>()))...>;
};

template<typename Func, typename Variant>
using visitor_result_t = typename visitor_result<Func, Variant>::type;

template<typename Func, typename Variant>
visitor_result_t<Func, Variant> generic_visit(Func func, Variant variant)
{
    return boost::apply_visitor([&](auto a) -> visitor_result_t<Func, Variant> { return func(a); }, variant);
}

Вживую

1 голос
/ 14 июня 2019

Опираясь на ответ @ Caleth, это позволяет использовать любой вариант без дублирования списка параметров.

#include <vector>
#include <variant>
#include <type_traits>

//Replace with more appropriate name
template<typename Variant, typename Lambda>
struct X_impl;
template<typename...Ts, typename Lambda>
struct X_impl<std::variant<Ts...>, Lambda>{
    using type = std::variant<std::invoke_result_t<Lambda,Ts>...>;
};
template<typename...Ts, typename Lambda>
struct X_impl<const std::variant<Ts...>, Lambda>{
    using type = std::variant<std::invoke_result_t<Lambda,const Ts>...>;
};

template<typename Variant, typename Lambda>
using X = typename X_impl<std::remove_reference_t<Variant>, Lambda>::type;


template<typename Variant, typename Lambda>
auto visit(Variant&& variant, Lambda&& lambda){
    auto wrapped_lambda = [&lambda](auto&& arg) -> X<Variant,Lambda>{ 
        using T = decltype(arg);
        return std::forward<Lambda>(lambda)(std::forward<T>(arg));
    };
    return std::visit(wrapped_lambda, std::forward<Variant>(variant));
}
int main()
{
    std::variant<std::vector<int>,const std::vector<double> > vr = std::vector<int>(5, 5);
    const std::variant<std::vector<int>,const std::vector<double> > c_vr = std::vector<int>(5, 5);
    auto& ref_vr = vr;
    auto& ref_c_vr = c_vr;

    auto visit_fnc = [](auto&& a){return a.begin();};
    visit(vr, visit_fnc);
    visit(c_vr, visit_fnc);
    visit(ref_vr, visit_fnc);
    visit(ref_c_vr, visit_fnc);
}

EDIT: О, похоже, @Caleth также добавил общее решение.

1 голос
/ 14 июня 2019

Используйте также вариант варианта результата. Другими словами:

boost::variant<std::vector<int>, std::vector<double> > vr 
    = std::vector<int>(5, 5);;

boost::apply_visitor([](auto a) -> boost::variant<int, double> {
    using T = std::decay_t<decltype(a)>;
    if constexpr (std::is_same_v<T, std::vector<int>>) {
         int v = 0;
         for(auto q : a) v += q;
         return v;
    }
    else if constexpr (std::is_same_v<T, std::vector<double>>) {
         double v = 0;
         for(auto q : a) v += q;
         return v;
    }
}, vr);

Я могу представить, как это сделать с автоматическим выводом правильного варианта возвращаемого типа, но это потребует большого количества кода.

0 голосов
/ 14 июня 2019

Я думаю, вам нужно действовать по-другому, итераторы в вашем текущем случае разные, но вы можете обойти это:

std::vector<boost::variant<int, double>> vr = std::vector<int>(5, 5);

Это в основном одно и то же.

...