Получить элемент std :: tuple как std :: option - PullRequest
5 голосов
/ 26 июня 2019

Учитывая тип варианта:

using Variant = std::variant<bool, char, int, float, double, std::string>;

и тип кортежа, содержащий элементы, ограниченные типами этого варианта (возможны дубликаты и пропуски, но без дополнительных типов):

using Tuple = std::tuple<char, int, int, double, std::string>;

Как реализовать методы, которые получают и устанавливают элемент кортежа по заданному индексу как Variant во время выполнения:

Variant Get(const Tuple & val, size_t index);
void Set(Tuple & val, size_t index, const Variant & elem_v);

У меня есть две реализации в моем коде, но у меня сложилось впечатление, что может быть лучше. Моя первая реализация использует std::function, а вторая создает массив из некоторых Accessor указателей, который накладывает ограничения на перемещение и копирование моего объекта (потому что его адрес изменяется). Интересно, знает ли кто-нибудь, как правильно это осуществить.

EDIT1:

Следующий пример, вероятно, проясняет, что я имею в виду:

Tuple t = std::make_tuple(1, 2, 3, 5.0 "abc");
Variant v = Get(t, 1);
assert(std::get<int>(v) == 2);
Set(t, 5, Variant("xyz"));
assert(std::get<5>(t) == std::string("xyz"));

Ответы [ 4 ]

7 голосов
/ 26 июня 2019

Я собираюсь продолжить мою тему рекомендации Boost.Mp11 для всех вещей метапрограммирования, потому что всегда есть функция для этого.В этом случае мы хотим mp_with_index.Эта функция поднимает индекс времени выполнения в индекс времени компиляции.

Variant Get(Tuple const& val, size_t index)
{
    return mp_with_index<std::tuple_size_v<Tuple>>(
        index,
        [&](auto I){ return Variant(std::get<I>(val)); }
        );
}

Учитывая, что в OP индексы Tuple и Variant даже не выстраиваются, Set должен фактическипосетите Variant вместо того, чтобы полагаться на индекс.Я использую is_assignable здесь в качестве ограничения, но это можно отрегулировать как подходящее для проблемы (например, возможно, это должно быть is_same).

void Set(Tuple& val, size_t index, Variant const& elem_v)
{
    mp_with_index<std::tuple_size_v<Tuple>>(
        index,
        [&](auto I){
            std::visit([&](auto const& alt){
                if constexpr (std::is_assignable_v<
                        std::tuple_element_t<Tuple, I>,
                        decltype(alt)>)
                {
                    std::get<I>(val) = alt;
                } else {
                    throw /* something */;
                }
            }, elem_v);
        });
}

Если вам требуется, чтобы каждый тип вTuple появляется ровно один раз в Variant, и вы хотите напрямую назначать только из этого типа без каких-либо преобразований, это можно упростить до:

void Set(Tuple& val, size_t index, Variant const& elem_v)
{
    mp_with_index<std::tuple_size_v<Tuple>>(
        index,
        [&](auto I){
            using T = std::tuple_element_t<Tuple, I>;
            std::get<I>(val) = std::get<T>(elem_v);
        });
}

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

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

Вот возможные реализации функций get_runtime и set_runtime, которые полагаются на рекурсию, чтобы попытаться сопоставить индекс времени выполнения с единицей времени компиляции:

template <class Variant, class Tuple, std::size_t Index = 0>
Variant get_runtime(Tuple &&tuple, std::size_t index) {
    if constexpr (Index == std::tuple_size_v<std::decay_t<Tuple>>) {
        throw "Index out of range for tuple";
    }
    else {
        if (index == Index) {
            return Variant{std::get<Index>(tuple)};
        }
        return get_runtime<Variant, Tuple, Index + 1>(
            std::forward<Tuple>(tuple), index);
    }
}


template <class Tuple, class Variant, std::size_t Index = 0>
void set_runtime(Tuple &tuple, std::size_t index, Variant const& variant) {
    if constexpr (Index == std::tuple_size_v<std::decay_t<Tuple>>) {
        throw "Index out of range for tuple";
    }
    else {
        if (index == Index) {
            // Note: You should check here that variant holds the correct type
            // before assigning.
            std::get<Index>(tuple) = 
                std::get<std::tuple_element_t<Index, Tuple>>(variant);
        }
        else {
            set_runtime<Tuple, Variant, Index + 1>(tuple, index, variant);
        }
    }
}

Вы можете использовать их как Get и Set:

using Variant = std::variant<bool, char, int, float, double, std::string>;
using Tuple = std::tuple<char, int, int, double, std::string>;

Tuple t = std::make_tuple(1, 2, 3, 5.0, "abc");
Variant v = get_runtime<Variant>(t, 1);
assert(std::get<int>(v) == 2);
set_runtime(t, 4, Variant("xyz"));
assert(std::get<4>(t) == std::string("xyz"));
1 голос
/ 26 июня 2019

Во-первых, некоторые машины.

alternative - это вариант интегральных констант, которые не имеют состояния.Затем мы можем использовать их для посещения, чтобы преобразовать ограниченное значение времени выполнения в значение времени компиляции.

template<class T, T...Is>
using alternative = std::variant< std::integral_constant<T, Is>... >;

template<class List>
struct alternative_from_sequence;
template<class T, T...Is>
struct alternative_from_sequence< std::integer_sequence<T,Is...> > {
  using type=alternative<T, Is...>;
};
template<class T, T Max>
using make_alternative = typename alternative_from_sequence<
  std::make_integer_sequence<T, Max>
>::type;

template<class T, T Max, T Cur=Max-1>
make_alternative<T, Max> get_alternative( T value, std::integral_constant< T, Max > ={} ) {
  if(Cur == 0 || value == Cur) {
    return std::integral_constant<T, Cur>{};
  }
  if constexpr (Cur > 0)
  {
    return get_alternative<T, Max, Cur-1>( value );
  }
}
template<class...Ts>
auto get_alternative( std::variant<Ts...> const& v ) {
    return get_alternative<std::size_t, sizeof...(Ts) >( v.index() );
}

Теперь ваша актуальная проблема.Это Get требует, чтобы вы передали свой Variant тип:

template<class Variant, class...Ts>
Variant Get(std::tuple<Ts...> const & val, size_t index) {
  auto which = get_alternative<std::size_t, sizeof...(Ts)>( index );
  return std::visit( [&val]( auto i )->Variant {
    return std::get<i>(val);
  }, which );
}

Ваша Set функция кажется токсичной;если типы не совпадают, практического обращения нет.Я добавлю возвращаемое значение, в котором будет указано, что присвоение не выполнено:

template<class...Ts, class...Vs>
bool Set(
  std::tuple<Ts...> & val,
  std::size_t index,
  const std::variant<Vs...>& elem_v
) {
  auto tuple_which = get_alternative<std::size_t, sizeof...(Ts)>( index );
  auto variant_which = get_alternative( elem_v );
  return std::visit( [&val, &elem_v](auto tuple_i, auto variant_i) {
    using variant_type = std::variant_alternative_t<variant_i, std::variant<Vs...>>;
    using tuple_type = std::tuple_element_t< tuple_i, std::tuple<Ts...> >;

    if constexpr (!std::is_assignable<tuple_type&, variant_type const&>{}) {
      return false;
    } else {
      std::get<tuple_i>(val) = std::get<variant_i>(elem_v);
      return true;
    }
  }, tuple_which, variant_which );
}

Этот Set возвращает значение false, если типы нельзя назначить.

Пример в реальном времени .

1 голос
/ 26 июня 2019
template <size_t... I>
Variant GetHelper(const Tuple& val, size_t index, std::index_sequence<I...>)
{
    Variant value;
    int temp[] = {
        ([&]
        {
            if (index == I)
                value = std::get<I>(val);
        }(), 0)... };
    return value;
}

Variant Get(const Tuple& val, size_t index)
{
    return GetHelper(val, index, std::make_index_sequence<std::tuple_size_v<Tuple>>{});
}

template <size_t... I>
void SetHelper(Tuple& val, size_t index, Variant elem_v, std::index_sequence<I...>)
{
    int temp[] = {
        ([&]
        {
            using type = std::tuple_element_t<I, Tuple>;
            if (index == I)
                std::get<I>(val) = std::get<type>(elem_v);
        }(), 0)... };
}

void Set(Tuple& val, size_t index, Variant elem_v)
{
    SetHelper(val, index, elem_v, std::make_index_sequence<std::tuple_size_v<Tuple>>{});
}

Пояснение:

Используйте std::index_sequence, чтобы получить доступ к каждому элементу кортежа через индекс постоянной времени компиляции I. Создайте лямбду для каждого индекса, которая выполняет желаемое действие, если индекс совпадает, и немедленно вызовите ее (обратите внимание на () сразу после лямбды). Используйте синтаксис int temp[] = { (some_void_func(), 0)... } для фактического вызова каждой лямбды (вы не можете использовать синтаксис распаковки ... непосредственно для функций void, поэтому этот трюк назначает его массиву int).

Кроме того, вы можете заставить свои лямбды возвращать фиктивный int. Тогда вы можете позвонить им, распаковав напрямую.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...