获取一个std :: tuple元素作为std :: variant

时间:2019-06-26 14:25:16

标签: c++ c++17

给出一个变体类型:

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 个答案:

答案 0 :(得分:7)

我将继续针对所有元编程内容推荐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中,元组和变体的索引甚至没有对齐,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);
        });
}

如果变体与该类型不相关,则会抛出该异常。

答案 1 :(得分:3)

以下是get_runtimeset_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);
        }
    }
}

您可以像使用GetSet一样使用它们:

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

答案 2 :(得分:1)

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来访问每个元组元素。为每个索引创建一个lambda,如果索引匹配,该lambda将执行所需的操作,并立即调用它(请注意lambda之后的())。使用语法int temp[] = { (some_void_func(), 0)... }实际调用每个lambda(您不能直接在void函数上使用解包语法...,因此这是将其分配给int数组的技巧。

或者,您可以让您的lambda返回一些虚拟int。然后,您可以通过直接打开包装来致电他们。

答案 3 :(得分:1)

首先,一些机械。

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。

Live example