我在boost::variant
中达到了50个类型的限制。我发现这个不错的自包含标题,但它缺乏多访问功能(我实际上需要双重访问)。
我试着照看一下它,但这样的方法似乎非常雄心勃勃,与我缺乏元编程经验相冲突…
如果你能指出一个预先准备好的变体实现,或者给一些建议来扩展我上面喜欢的那个,那就太好了,谢谢!
致philip rossamen和upvoters:在这里你可以找到一个我正在考虑的设计的基本例子。请随意添加更深入的评论
编辑:Boost现在支持多访问,就像c++ 17变体一样。
如果你有一个一元visit
成员函数的变体类型,它可以扩展到一个n元- apply_visitor
函数,如下所示:
包含必要的标准库依赖项:
#include <tuple>
#include <type_traits>
#include <utility> //For C++14 `std::integer_sequence`.
//If you don't want to use C++14, write your own.
现在是一个帮助函数,用于创建与现有元组相同的新元组,但不包含第一个元素:
template<std::size_t ...S, typename Head, typename ...Tail>
std::tuple<Tail...> tuple_tail_impl(
index_sequence<S...>,
std::tuple<Head, Tail...> const &in_tuple)
{
struct In {
template<std::size_t N>
using ElementType =
typename std::tuple_element<N, std::tuple<Head, Tail...>>::type;
};
return std::tuple<Tail...>(
std::forward<In::ElementType<S+1>>(std::get<S+1>(in_tuple))...);
}
template<typename Head, typename ...Tail>
std::tuple<Tail...> tuple_tail(std::tuple<Head, Tail...> const& in_tuple) {
return tuple_tail_impl(index_sequence_for<Tail...>(), in_tuple);
}
现在,完成工作的类和用于创建该类的辅助函数:
template<typename Visitor, typename MatchedValueTuple, typename... TailVariants>
struct NAryVisitorFlattener;
template<typename Visitor, typename MatchedValueTuple, typename... TailVariants>
NAryVisitorFlattener<Visitor, MatchedValueTuple, TailVariants...>
make_NAryVisitorFlattener(
Visitor &&visitor,
MatchedValueTuple &&matchedValues,
std::tuple<TailVariants...> &&tailVariants);
在递归情况下,NAryVisitorFlattener
依次调用每个变量的apply
成员函数,并在MatchedValueTuple
中构建结果值。
template<
typename Visitor,
typename MatchedValueTuple,
typename CurrentVariant,
typename... TailVariants>
struct NAryVisitorFlattener<
Visitor, MatchedValueTuple, CurrentVariant, TailVariants...>
{
typedef typename
std::remove_reference<Visitor>::type::result_type result_type;
Visitor visitor;
MatchedValueTuple matchedValues;
std::tuple<CurrentVariant, TailVariants...> tailVariants;
template<typename A>
result_type operator()(A &&a)
{
auto flattener = make_NAryVisitorFlattener(
std::forward<Visitor>(visitor),
std::tuple_cat(matchedValues, std::forward_as_tuple(std::forward<A>(a))),
tuple_tail(tailVariants));
return std::forward<CurrentVariant>(std::get<0>(tailVariants))
.visit(flattener);
}
};
在基本情况下,每个变量都调用了apply
,访问器调用了MatchedValueTuple
中的值:
template<typename Visitor, typename MatchedValueTuple>
struct NAryVisitorFlattener<Visitor, MatchedValueTuple> {
typedef typename
std::remove_reference<Visitor>::type::result_type result_type;
Visitor visitor;
MatchedValueTuple matchedValues;
std::tuple<> tailVariants;
template<typename A>
result_type operator()(A &&a) {
return callFunc(
std::make_index_sequence<std::tuple_size<MatchedValueTuple>::value>(),
std::forward<A>(a));
}
template<std::size_t N>
using MatchedValueType =
typename std::tuple_element<N,MatchedValueTuple>::type;
template<std::size_t ...S, typename A>
result_type callFunc(std::index_sequence<S...>, A &&a) {
return std::forward<Visitor>(visitor)(
std::forward<MatchedValueType<S>>(matchedValues))...,
std::forward<A>(a));
}
};
和前面声明的辅助函数的定义:
template<typename Visitor, typename MatchedValueTuple, typename... TailVariants>
NAryVisitorFlattener<Visitor, MatchedValueTuple, TailVariants...>
make_NAryVisitorFlattener(
Visitor &&visitor,
MatchedValueTuple &&matchedValues,
std::tuple<TailVariants...> &&tailVariants)
{
return {
std::forward<Visitor>(visitor),
std::forward<MatchedValueTuple>(matchedValues),
std::forward<std::tuple<TailVariants...>>(tailVariants)
};
}
现在,您一直在等待的功能。从第一个NAryVisitorFlattener
:
template<typename Visitor, typename VariantA, typename... Variants>
typename std::remove_reference<Visitor>::type::result_type
apply_visitor(Visitor &&visitor, VariantA &&variantA, Variants &&...variants) {
auto flattener = make_NAryVisitorFlattener(
std::forward<Visitor>(visitor),
std::tuple<>{},
std::forward_as_tuple(std::forward<Variants>(variants)...));
return std::forward<VariantA>(variantA).visit(flattener);
}
这些都是从我的完整的c++ 11兼容的变体实现中获得的。
好吧,既然我对这个问题很感兴趣,我必须承认我在玩弄它。
为了克服Boost中的限制,我在可变模板方面实现了一个轻量级原型级变体,我不会在这里链接(完整的代码可以在Coliru上获得)。
相反,我将链接到一个简单的多访问的实现。它并不完美,特别是因为它不尊重左值/右值。但是,它具有简单的概念优势,因此易于理解。如果您想跳过代码,请随意,它在底部。以下是之前的一些解释。
第一个技巧是通过一个指向函数的指针数组实现快速动态分派:创建一个具有统一签名的静态模板函数,它将根据一种类型
处理参数。一个这样的函数数组被实例化,变量模板包
每个元素一个元素在数组中的索引是通过运行时索引
完成的。
这归结为:
template <size_t N>
void print_number() { std::cout << N << "n"; }
template <size_t... Is>
void doit(size_t index) {
using Printer = void (*)();
static Printer const AllPrinters[] = { &printer_number<Is>... };
Printer const printer = AllPrinters[index];
printer();
}
数组应该放在.rodata
中(它是常量)。这与v-ptr/v-table调度略有不同,因为索引是运行时索引,而v-ptr/v-table调度通常在编译时知道索引(表中的第三个方法)。
一次调度一个变量,将其他变量按原样传递
使用堆栈和递归一次一个地收集参数
将所有参数转发给基访问器
在我的实现中转发访问者是NaryVisitor
,共递归是通过apply_nary_visitor_impl
和NaryApplier
之间的乒乓来实现的:
- 前者使用上面的数组技巧在调用 上的内容的变体中找到正确的类型。
- 后者在
NaryVisitor
适配器中包装访问者和获得的引用,并在剩余变量的N-1列表上递归
共递归实际上是解包二维变量的典型方法。
注意:可能有希望改进实现以保持l值和r值的区别,但是已经将其编译为相当大的战斗…
n元访问的完整代码:
namespace internal {
template <typename Visitor, typename T>
struct NaryVisitor {
using result_type = typename Visitor::result_type;
NaryVisitor(Visitor& visitor, T& t): visitor(visitor), ref(t) {}
Visitor& visitor;
T& ref;
template <typename... Args>
auto operator()(Args&&... args) -> result_type {
return visitor(ref, std::forward<Args>(args)...);
} // apply
}; // struct NaryVisitor
template <typename Visitor, typename T0, typename... Ts, typename... Vs>
auto apply_nary_visitor_impl(
Visitor& visitor, variant<T0, Ts...>&& v0, Vs&&... vs
)
-> typename Visitor::result_type;
template <typename Visitor, typename Variant>
struct NaryApplier {
using result_type = typename Visitor::result_type;
NaryApplier(Visitor& visitor, Variant& variant):
visitor(visitor), variant(variant) {}
Visitor& visitor;
Variant& variant;
template <typename T>
auto apply() -> result_type {
return visitor(*variant.template get<T>());
}
template <typename T, typename V0, typename... Vs>
auto apply(V0&& v0, Vs&&... vs) -> result_type {
NaryVisitor<Visitor, T> nary{
visitor,
*variant.template get<T>()
};
return apply_nary_visitor_impl(nary,
std::forward<V0>(v0),
std::forward<Vs>(vs)...);
}
}; // struct NaryApplier
template <typename Visitor, typename T0, typename... Ts, typename... Vs>
auto apply_nary_visitor_impl(
Visitor& visitor, variant<T0, Ts...>& v0, Vs&&... vs
)
-> typename Visitor::result_type
{
using result_type = typename Visitor::result_type;
using Variant = variant<T0, Types...>;
using Applier = internal::NaryApplier<Visitor, Variant>;
using Member = result_type (Applier::*)(Vs&&...);
static Member const members[] = {
(&Applier::template apply<T0, Vs...>),
(&Applier::template apply<Types, Vs...>)...
};
Member const m = members[v0.which()];
Applier a{visitor, v0};
return (a.*m)(std::forward<Vs>(vs)...);
} // apply_nary_visitor_impl
} // namespace internal
template <typename Visitor, typename... Variants>
auto apply_nary_visitor(Visitor&& visitor, Variants&&... vs)
-> typename Visitor::result_type
{
return internal::apply_nary_visitor_impl(visitor,
std::forward<Variants>(vs)...);
} // apply_nary_visitor