给出一个变体类型:
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"));
答案 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_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"));
答案 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。