Существует ли вариант вариационного шаблона с методом многократного посещения?

Я попал в предел 50 типов в boost::variant. Я нашел этот приятный автономный заголовок, но ему не хватает функции многократного посещения (мне действительно нужно двойное посещение).

Я попытался немного посмотреть после этого, но такой метод кажется очень амбициозным и сталкивается с моим недостатком опыта с метапрограммированием...

Было бы замечательно, если бы вы могли указать на предустановленную реализацию варианта или дать некоторые советы, чтобы расширить тот, который мне понравился выше, спасибо!


В Filip Roséen и upvoters: здесь вы найдете базовый пример дизайна, который я рассматриваю. Не стесняйтесь добавлять более подробные комментарии об этом.

Ответ 1

EDIT: Boost теперь поддерживает multi-visitation, как и Вариант С++ 17.


Если у вас есть тип варианта с унарной функцией-членом 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);
}

Это все взято из моей полной версии совместимого варианта с С++ 11 здесь.

Ответ 2

Хорошо, поскольку меня интересовала проблема, я должен признать, что поиграю с ней.

Чтобы преодолеть ограничение в Boost, я реализовал вариант облегченного прототипа уровня с точки зрения вариативных шаблонов, которые я не буду ссылаться здесь (полный код доступно на 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 индекс обычно известен во время компиляции (третий метод в таблице).

Второй трюк реализует n-арный визит как последовательность унаследованных посещений:

  • отправлять один вариант за раз, передавая другие варианты as-is

  • используйте стек и рекурсию для сбора аргументов один раз

  • пересылать все аргументы базовому посетителю

Переназначающий посетитель в моей реализации 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