是否存在具有多种访问方法的可变参数模板变体?

时间:2014-06-04 16:28:58

标签: c++ templates c++11 variadic-templates variant

我在boost::variant中达到了50种类型的限制。我找到了this nice self-contained header,但它没有多次访问功能(我实际上需要双重访问)。

我试着看一下它,但这种方法似乎非常雄心勃勃,与我缺乏元编程经验相冲突......

如果您可以指出预先制作的变体实现或者提供一些建议来扩展我喜欢的那个,那将是非常好的,谢谢!


ToFilipRoséen和upvoters:here你找到了我正在考虑的设计的基本例子。随意添加更多关于此的深入评论。

2 个答案:

答案 0 :(得分:9)

编辑:Boost现在支持multi-visitationC++17 variant也支持。


如果您的变体类型具有一元visit成员函数,则可以将其扩展为n-ary - 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兼容变体实现here

答案 1 :(得分:4)

好的,既然我对这个问题感兴趣,我必须承认要玩它。

为了克服Boost中的限制,我在可变参数模板方面实现了一个轻量级原型级变体,我将不在此处链接(完整代码为available on Coliru)。

相反,我将链接到多访问的简单实现。它并不完美,特别是因为它不尊重l值/ r值。尽管如此,它具有简单的概念优势,因此易于理解。

如果您希望跳过代码,请随意,它位于底部。以下是一些解释。

第一个技巧是使用指向函数的指针数组来完成快速动态调度:

  • 创建一个具有统一签名的静态模板函数,该函数将根据一种类型

  • 处理参数
  • 实例化此类函数的数组,每个元组的可变参数模板包含一个元素

  • 数组中的索引是通过运行时索引

  • 完成的

归结为:

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分派,索引通常在编译时已知(第3次)表中的方法)。

第二个技巧是实施n-ary访问作为一连串的一元式访问:

  • 一次发送一个变体,按原样传递其他变体

  • 使用堆栈和递归来一次收集参数

  • 将所有参数转发给基本访问者

我的实施中的转发访问者是NaryVisitor,共同递归是通过apply_nary_visitor_implNaryApplier之间的乒乓实现的:

  • 前者使用上面的数组技巧在变体中找到正确的类型以调用
  • 上的内容
  • 后者包装访问者并在NaryVisitor适配器中获取引用,并递归到N-1个剩余变体列表

共同递归实际上是解包二维变量的典型方法。

注意:可能有希望改进实现以保持l值和r值的区别,但已经将其编译为一场战斗......


n-ary访问的完整代码:

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